diff --git a/.gitignore b/.gitignore index 0030f22363c2..91a2647ca98f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ no_llvm_build /llvm/ /mingw-build/ /build -/build-rust-analyzer/ +/build-rust-analyzer /dist/ /unicode-downloads /target diff --git a/Cargo.lock b/Cargo.lock index 4a65021c1a21..9350112939bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025c7edcdffa4ccc5c0905f472a0ae3759378cfbef88ef518a3575e19ae3aebd" +checksum = "a44baf24dd94e781f74dfe67ffee75a09a57971ddf0f615a178b4f6d404b48ff" dependencies = [ "anstyle", "unicode-width 0.2.2", @@ -3766,7 +3766,7 @@ dependencies = [ name = "rustc_errors" version = "0.0.0" dependencies = [ - "annotate-snippets 0.12.8", + "annotate-snippets 0.12.9", "anstream", "anstyle", "derive_setters", @@ -6070,9 +6070,9 @@ dependencies = [ [[package]] name = "wasi-preview1-component-adapter-provider" -version = "37.0.2" +version = "38.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0fcd636ad2b29a7c0490799a23ad61d1c8dedfafdb970447fddd0549502b60" +checksum = "7ec3ef3783e18f2457796ed91b1e6c2adc46f2905f740d1527ab3053fe8e5682" [[package]] name = "wasm-bindgen" @@ -6134,9 +6134,9 @@ dependencies = [ [[package]] name = "wasm-component-ld" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f565dfcfd9aabb10d865b608a92ce1f93051aeb56f4c89550ed9cd97d8ce0e" +checksum = "4bfc50dd0b883d841bc1dba5ff7020ca52fa7b2c3bb1266d8bf6a09dd032e115" dependencies = [ "anyhow", "clap", @@ -6144,7 +6144,7 @@ dependencies = [ "libc", "tempfile", "wasi-preview1-component-adapter-provider", - "wasmparser 0.240.0", + "wasmparser 0.241.2", "wat", "windows-sys 0.61.2", "winsplit", @@ -6171,24 +6171,24 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +checksum = "e01164c9dda68301e34fdae536c23ed6fe90ce6d97213ccc171eebbd3d02d6b8" dependencies = [ "leb128fmt", - "wasmparser 0.240.0", + "wasmparser 0.241.2", ] [[package]] name = "wasm-metadata" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee093e1e1ccffa005b9b778f7a10ccfd58e25a20eccad294a1a93168d076befb" +checksum = "876fe286f2fa416386deedebe8407e6f19e0b5aeaef3d03161e77a15fa80f167" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.240.0", - "wasmparser 0.240.0", + "wasm-encoder 0.241.2", + "wasmparser 0.241.2", ] [[package]] @@ -6213,9 +6213,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +checksum = "46d90019b1afd4b808c263e428de644f3003691f243387d30d673211ee0cb8e8" dependencies = [ "bitflags", "hashbrown", @@ -6226,22 +6226,22 @@ dependencies = [ [[package]] name = "wast" -version = "240.0.0" +version = "241.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0efe1c93db4ac562b9733e3dca19ed7fc878dba29aef22245acf84f13da4a19" +checksum = "63f66e07e2ddf531fef6344dbf94d112df7c2f23ed6ffb10962e711500b8d816" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.2", - "wasm-encoder 0.240.0", + "wasm-encoder 0.241.2", ] [[package]] name = "wat" -version = "1.240.0" +version = "1.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec9b6eab7ecd4d639d78515e9ea491c9bacf494aa5eda10823bd35992cf8c1e" +checksum = "45f923705c40830af909c5dec2352ec2821202e4a66008194585e1917458a26d" dependencies = [ "wast", ] @@ -6679,9 +6679,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc5474b078addc5fe8a72736de8da3acfb3ff324c2491133f8b59594afa1a20" +checksum = "1fd0c57df25e7ee612d946d3b7646c1ddb2310f8280aa2c17e543b66e0812241" dependencies = [ "anyhow", "bitflags", @@ -6690,17 +6690,17 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.240.0", + "wasm-encoder 0.241.2", "wasm-metadata", - "wasmparser 0.240.0", + "wasmparser 0.241.2", "wit-parser", ] [[package]] name = "wit-parser" -version = "0.240.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9875ea3fa272f57cc1fc50f225a7b94021a7878c484b33792bccad0d93223439" +checksum = "09ef1c6ad67f35c831abd4039c02894de97034100899614d1c44e2268ad01c91" dependencies = [ "anyhow", "id-arena", @@ -6711,7 +6711,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.240.0", + "wasmparser 0.241.2", ] [[package]] diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 0d0f5e405d1f..5318f9a507a5 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -59,7 +59,10 @@ # toolchain or changing LLVM locally, you probably want to leave this enabled. # # Set this to `true` to download if CI llvm available otherwise it builds -# from `src/llvm-project`. +# from `src/llvm-project`. If you set it to `true`, it's safe and time-saving to run +# `git submodule deinit src/llvm-project` to avoid git updating the llvm-project submodule +# when building compiler locally. +# # # Set this to `"if-unchanged"` to download only if the llvm-project has not # been modified. You can also use this if you are unsure whether you're on a diff --git a/compiler/rustc_abi/src/extern_abi.rs b/compiler/rustc_abi/src/extern_abi.rs index e3b2b1eff72d..6a5ea36f2a42 100644 --- a/compiler/rustc_abi/src/extern_abi.rs +++ b/compiler/rustc_abi/src/extern_abi.rs @@ -269,13 +269,56 @@ pub fn supports_c_variadic(self) -> CVariadicStatus { | Self::Aapcs { .. } | Self::Win64 { .. } | Self::SysV64 { .. } - | Self::EfiApi => CVariadicStatus::Stable, - Self::System { .. } => { - CVariadicStatus::Unstable { feature: rustc_span::sym::extern_system_varargs } - } + | Self::EfiApi + | Self::System { .. } => CVariadicStatus::Stable, _ => CVariadicStatus::NotSupported, } } + + /// Returns whether the ABI supports guaranteed tail calls. + #[cfg(feature = "nightly")] + pub fn supports_guaranteed_tail_call(self) -> bool { + match self { + Self::CmseNonSecureCall | Self::CmseNonSecureEntry => { + // See https://godbolt.org/z/9jhdeqErv. The CMSE calling conventions clear registers + // before returning, and hence cannot guarantee a tail call. + false + } + Self::AvrInterrupt + | Self::AvrNonBlockingInterrupt + | Self::Msp430Interrupt + | Self::RiscvInterruptM + | Self::RiscvInterruptS + | Self::X86Interrupt => { + // See https://godbolt.org/z/Edfjnxxcq. Interrupts cannot be called directly. + false + } + Self::GpuKernel | Self::PtxKernel => { + // See https://godbolt.org/z/jq5TE5jK1. + false + } + Self::Custom => { + // This ABI does not support calls at all (except via assembly). + false + } + Self::C { .. } + | Self::System { .. } + | Self::Rust + | Self::RustCall + | Self::RustCold + | Self::RustInvalid + | Self::Unadjusted + | Self::EfiApi + | Self::Aapcs { .. } + | Self::Cdecl { .. } + | Self::Stdcall { .. } + | Self::Fastcall { .. } + | Self::Thiscall { .. } + | Self::Vectorcall { .. } + | Self::SysV64 { .. } + | Self::Win64 { .. } => true, + } + } } pub fn all_names() -> Vec<&'static str> { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index f904b2b670a2..524f8b054cb4 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1929,7 +1929,7 @@ fn lower_expr_for( /// ControlFlow::Break(residual) => /// #[allow(unreachable_code)] /// // If there is an enclosing `try {...}`: - /// break 'catch_target Try::from_residual(residual), + /// break 'catch_target Residual::into_try_type(residual), /// // Otherwise: /// return Try::from_residual(residual), /// } @@ -1979,7 +1979,11 @@ fn lower_expr_try(&mut self, span: Span, sub_expr: &Expr) -> hir::ExprKind<'hir> let (residual_local, residual_local_nid) = self.pat_ident(try_span, residual_ident); let residual_expr = self.expr_ident_mut(try_span, residual_ident, residual_local_nid); let from_residual_expr = self.wrap_in_try_constructor( - hir::LangItem::TryTraitFromResidual, + if self.catch_scope.is_some() { + hir::LangItem::ResidualIntoTryType + } else { + hir::LangItem::TryTraitFromResidual + }, try_span, self.arena.alloc(residual_expr), unstable_span, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 9cb17ea67a37..1f36454ec861 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -183,7 +183,12 @@ fn new(tcx: TyCtxt<'hir>, resolver: &'a mut ResolverAstLowering) -> Self { impl_trait_defs: Vec::new(), impl_trait_bounds: Vec::new(), allow_contracts: [sym::contracts_internals].into(), - allow_try_trait: [sym::try_trait_v2, sym::yeet_desugar_details].into(), + allow_try_trait: [ + sym::try_trait_v2, + sym::try_trait_v2_residual, + sym::yeet_desugar_details, + ] + .into(), allow_pattern_type: [sym::pattern_types, sym::pattern_type_range_trait].into(), allow_gen_future: if tcx.features().async_fn_track_caller() { [sym::gen_future, sym::closure_track_caller].into() diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index c55fe3fad008..fde7dd6ef7a8 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -683,7 +683,7 @@ fn handle_offload<'ll>(cx: &'ll SimpleCx<'_>, old_fn: &llvm::Value) { // Here we map the old arguments to the new arguments, with an offset of 1 to make sure // that we don't use the newly added `%dyn_ptr`. unsafe { - llvm::LLVMRustOffloadMapper(cx.llmod(), old_fn, new_fn); + llvm::LLVMRustOffloadMapper(old_fn, new_fn); } llvm::set_linkage(new_fn, llvm::get_linkage(old_fn)); diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index f0c1dfc53aca..b10a1282f4dd 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -760,30 +760,18 @@ fn write_operand_repeatedly( count: u64, dest: PlaceRef<'tcx, &'ll Value>, ) { - let zero = self.const_usize(0); - let count = self.const_usize(count); - - let header_bb = self.append_sibling_block("repeat_loop_header"); - let body_bb = self.append_sibling_block("repeat_loop_body"); - let next_bb = self.append_sibling_block("repeat_loop_next"); - - self.br(header_bb); - - let mut header_bx = Self::build(self.cx, header_bb); - let i = header_bx.phi(self.val_ty(zero), &[zero], &[self.llbb()]); - - let keep_going = header_bx.icmp(IntPredicate::IntULT, i, count); - header_bx.cond_br(keep_going, body_bb, next_bb); - - let mut body_bx = Self::build(self.cx, body_bb); - let dest_elem = dest.project_index(&mut body_bx, i); - cg_elem.val.store(&mut body_bx, dest_elem); - - let next = body_bx.unchecked_uadd(i, self.const_usize(1)); - body_bx.br(header_bb); - header_bx.add_incoming_to_phi(i, next, body_bb); - - *self = Self::build(self.cx, next_bb); + if self.cx.sess().opts.optimize == OptLevel::No { + // To let debuggers single-step over lines like + // + // let foo = ["bar"; 42]; + // + // we need the debugger-friendly LLVM IR that `_unoptimized()` + // provides. The `_optimized()` version generates trickier LLVM IR. + // See PR #148058 for a failed attempt at handling that. + self.write_operand_repeatedly_unoptimized(cg_elem, count, dest); + } else { + self.write_operand_repeatedly_optimized(cg_elem, count, dest); + } } fn range_metadata(&mut self, load: &'ll Value, range: WrappingRange) { @@ -1514,6 +1502,78 @@ pub(crate) fn set_unpredictable(&mut self, inst: &'ll Value) { self.set_metadata_node(inst, llvm::MD_unpredictable, &[]); } + fn write_operand_repeatedly_optimized( + &mut self, + cg_elem: OperandRef<'tcx, &'ll Value>, + count: u64, + dest: PlaceRef<'tcx, &'ll Value>, + ) { + let zero = self.const_usize(0); + let count = self.const_usize(count); + + let header_bb = self.append_sibling_block("repeat_loop_header"); + let body_bb = self.append_sibling_block("repeat_loop_body"); + let next_bb = self.append_sibling_block("repeat_loop_next"); + + self.br(header_bb); + + let mut header_bx = Self::build(self.cx, header_bb); + let i = header_bx.phi(self.val_ty(zero), &[zero], &[self.llbb()]); + + let keep_going = header_bx.icmp(IntPredicate::IntULT, i, count); + header_bx.cond_br(keep_going, body_bb, next_bb); + + let mut body_bx = Self::build(self.cx, body_bb); + let dest_elem = dest.project_index(&mut body_bx, i); + cg_elem.val.store(&mut body_bx, dest_elem); + + let next = body_bx.unchecked_uadd(i, self.const_usize(1)); + body_bx.br(header_bb); + header_bx.add_incoming_to_phi(i, next, body_bb); + + *self = Self::build(self.cx, next_bb); + } + + fn write_operand_repeatedly_unoptimized( + &mut self, + cg_elem: OperandRef<'tcx, &'ll Value>, + count: u64, + dest: PlaceRef<'tcx, &'ll Value>, + ) { + let zero = self.const_usize(0); + let count = self.const_usize(count); + let start = dest.project_index(self, zero).val.llval; + let end = dest.project_index(self, count).val.llval; + + let header_bb = self.append_sibling_block("repeat_loop_header"); + let body_bb = self.append_sibling_block("repeat_loop_body"); + let next_bb = self.append_sibling_block("repeat_loop_next"); + + self.br(header_bb); + + let mut header_bx = Self::build(self.cx, header_bb); + let current = header_bx.phi(self.val_ty(start), &[start], &[self.llbb()]); + + let keep_going = header_bx.icmp(IntPredicate::IntNE, current, end); + header_bx.cond_br(keep_going, body_bb, next_bb); + + let mut body_bx = Self::build(self.cx, body_bb); + let align = dest.val.align.restrict_for_offset(dest.layout.field(self.cx(), 0).size); + cg_elem + .val + .store(&mut body_bx, PlaceRef::new_sized_aligned(current, cg_elem.layout, align)); + + let next = body_bx.inbounds_gep( + self.backend_type(cg_elem.layout), + current, + &[self.const_usize(1)], + ); + body_bx.br(header_bb); + header_bx.add_incoming_to_phi(current, next, body_bb); + + *self = Self::build(self.cx, next_bb); + } + pub(crate) fn minnum(&mut self, lhs: &'ll Value, rhs: &'ll Value) -> &'ll Value { self.call_intrinsic("llvm.minnum", &[self.val_ty(lhs)], &[lhs, rhs]) } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 16549f9aab81..ca64d96c2a33 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2025,7 +2025,7 @@ pub(crate) fn LLVMRustCreateRangeAttribute( ) -> &Attribute; // Operations on functions - pub(crate) fn LLVMRustOffloadMapper<'a>(M: &'a Module, Fn: &'a Value, Fn: &'a Value); + pub(crate) fn LLVMRustOffloadMapper<'a>(Fn: &'a Value, Fn: &'a Value); pub(crate) fn LLVMRustGetOrInsertFunction<'a>( M: &'a Module, Name: *const c_char, diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 323e1cefd586..bac3a9da48d9 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -1498,14 +1498,10 @@ pub fn mem_copy_repeatedly( // Prepare getting source provenance. let src_bytes = src_alloc.get_bytes_unchecked(src_range).as_ptr(); // raw ptr, so we can also get a ptr to the destination allocation - // first copy the provenance to a temporary buffer, because - // `get_bytes_mut` will clear the provenance, which is correct, - // since we don't want to keep any provenance at the target. - // This will also error if copying partial provenance is not supported. - let provenance = src_alloc - .provenance() - .prepare_copy(src_range, self) - .map_err(|e| e.to_interp_error(src_alloc_id))?; + // First copy the provenance to a temporary buffer, because + // `get_bytes_unchecked_for_overwrite_ptr` will clear the provenance (in preparation for + // inserting the new provenance), and that can overlap with the source range. + let provenance = src_alloc.provenance_prepare_copy(src_range, self); // Prepare a copy of the initialization mask. let init = src_alloc.init_mask().prepare_copy(src_range); diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index cd34892f029c..a409c7fad417 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -704,6 +704,7 @@ fn write_immediate_to_mplace_no_validate( // wrong type. let tcx = *self.tcx; + let will_later_validate = M::enforce_validity(self, layout); let Some(mut alloc) = self.get_place_alloc_mut(&MPlaceTy { mplace: dest, layout })? else { // zero-sized access return interp_ok(()); @@ -714,23 +715,31 @@ fn write_immediate_to_mplace_no_validate( alloc.write_scalar(alloc_range(Size::ZERO, scalar.size()), scalar)?; } Immediate::ScalarPair(a_val, b_val) => { - let BackendRepr::ScalarPair(a, b) = layout.backend_repr else { + let BackendRepr::ScalarPair(_a, b) = layout.backend_repr else { span_bug!( self.cur_span(), "write_immediate_to_mplace: invalid ScalarPair layout: {:#?}", layout ) }; - let b_offset = a.size(&tcx).align_to(b.align(&tcx).abi); + let a_size = a_val.size(); + let b_offset = a_size.align_to(b.align(&tcx).abi); assert!(b_offset.bytes() > 0); // in `operand_field` we use the offset to tell apart the fields // It is tempting to verify `b_offset` against `layout.fields.offset(1)`, // but that does not work: We could be a newtype around a pair, then the // fields do not match the `ScalarPair` components. - alloc.write_scalar(alloc_range(Size::ZERO, a_val.size()), a_val)?; + // In preparation, if we do *not* later reset the padding, we clear the entire + // destination now to ensure that no stray pointer fragments are being + // preserved (see ). + // We can skip this if there is no padding (e.g. for wide pointers). + if !will_later_validate && a_size + b_val.size() != layout.size { + alloc.write_uninit_full(); + } + + alloc.write_scalar(alloc_range(Size::ZERO, a_size), a_val)?; alloc.write_scalar(alloc_range(b_offset, b_val.size()), b_val)?; - // We don't have to reset padding here, `write_immediate` will anyway do a validation run. } Immediate::Uninit => alloc.write_uninit_full(), } diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index 6606092e421e..b3f76732a602 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start -annotate-snippets = "0.12.8" +annotate-snippets = "0.12.9" anstream = "0.6.20" anstyle = "1.0.13" derive_setters = "0.1.6" diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 810a5a21a055..946f17943fe3 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1099,6 +1099,9 @@ pub fn expn_data( pub struct DeriveResolution { pub path: ast::Path, pub item: Annotatable, + // FIXME: currently this field is only used in `is_none`/`is_some` conditions. However, the + // `Arc` will be used if the FIXME in `MacroExpander::fully_expand_fragment` + // is completed. pub exts: Option>, pub is_const: bool, } diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs index 0ee4ad409e4b..5af47d51b711 100644 --- a/compiler/rustc_feature/src/accepted.rs +++ b/compiler/rustc_feature/src/accepted.rs @@ -215,6 +215,8 @@ macro_rules! declare_features { (accepted, extern_crate_self, "1.34.0", Some(56409)), /// Allows access to crate names passed via `--extern` through prelude. (accepted, extern_prelude, "1.30.0", Some(44660)), + /// Allows using `system` as a calling convention with varargs. + (accepted, extern_system_varargs, "CURRENT_RUSTC_VERSION", Some(136946)), /// Allows using F16C intrinsics from `core::arch::{x86, x86_64}`. (accepted, f16c_target_feature, "1.68.0", Some(44839)), /// Allows field shorthands (`x` meaning `x: x`) in struct literal expressions. diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index f51bc7031c31..26612fda5a7f 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -501,8 +501,6 @@ pub fn internal(&self, feature: Symbol) -> bool { (incomplete, explicit_tail_calls, "1.72.0", Some(112788)), /// Allows using `#[export_stable]` which indicates that an item is exportable. (incomplete, export_stable, "1.88.0", Some(139939)), - /// Allows using `system` as a calling convention with varargs. - (unstable, extern_system_varargs, "1.86.0", Some(136946)), /// Allows defining `extern type`s. (unstable, extern_types, "1.23.0", Some(43467)), /// Allow using 128-bit (quad precision) floating point numbers. diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index ed0cf2716028..0a6e491227d0 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -370,6 +370,7 @@ pub fn extract(attrs: &[impl AttributeExt]) -> Option<(Symbol, Span)> { TryTraitFromOutput, sym::from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None; + ResidualIntoTryType, sym::into_try_type, into_try_type_fn, Target::Fn, GenericRequirement::None; CoercePointeeValidated, sym::coerce_pointee_validated, coerce_pointee_validated_trait, Target::Trait, GenericRequirement::Exact(0); diff --git a/compiler/rustc_hir_typeck/src/inline_asm.rs b/compiler/rustc_hir_typeck/src/inline_asm.rs index c0cd23be6909..6460bd72c797 100644 --- a/compiler/rustc_hir_typeck/src/inline_asm.rs +++ b/compiler/rustc_hir_typeck/src/inline_asm.rs @@ -7,7 +7,7 @@ use rustc_middle::ty::{self, Article, FloatTy, IntTy, Ty, TyCtxt, TypeVisitableExt, UintTy}; use rustc_session::lint; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; use rustc_target::asm::{ InlineAsmReg, InlineAsmRegClass, InlineAsmRegOrRegClass, InlineAsmType, ModifierInfo, }; @@ -27,6 +27,7 @@ enum NonAsmTypeReason<'tcx> { InvalidElement(DefId, Ty<'tcx>), NotSizedPtr(Ty<'tcx>), EmptySIMDArray(Ty<'tcx>), + Tainted(ErrorGuaranteed), } impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { @@ -93,6 +94,14 @@ fn get_asm_ty( } } ty::Adt(adt, args) if adt.repr().simd() => { + if !adt.is_struct() { + let guar = self.fcx.dcx().span_delayed_bug( + span, + format!("repr(simd) should only be used on structs, got {}", adt.descr()), + ); + return Err(NonAsmTypeReason::Tainted(guar)); + } + let fields = &adt.non_enum_variant().fields; if fields.is_empty() { return Err(NonAsmTypeReason::EmptySIMDArray(ty)); @@ -234,6 +243,9 @@ fn check_asm_operand_type( let msg = format!("use of empty SIMD vector `{ty}`"); self.fcx.dcx().struct_span_err(expr.span, msg).emit(); } + NonAsmTypeReason::Tainted(_error_guard) => { + // An error has already been reported. + } } return None; } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index e68db4f44ca4..ddfec9f886a6 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -596,7 +596,9 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P .map(|fmap| { ( escape_dep_filename(&fmap.name.prefer_local().to_string()), - fmap.source_len.0 as u64, + // This needs to be unnormalized, + // as external tools wouldn't know how rustc normalizes them + fmap.unnormalized_source_len as u64, fmap.checksum_hash, ) }) diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index ade7ec38fb35..2e100a6215c0 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -252,7 +252,7 @@ pub(crate) fn run_in_thread_pool_with_globals< let query_map = rustc_span::set_session_globals_then(unsafe { &*(session_globals as *const SessionGlobals) }, || { // Ensure there was no errors collecting all active jobs. // We need the complete map to ensure we find a cycle to break. - QueryCtxt::new(tcx).collect_active_jobs().expect("failed to collect active queries in deadlock handler") + QueryCtxt::new(tcx).collect_active_jobs(false).expect("failed to collect active queries in deadlock handler") }); break_query_cycles(query_map, ®istry); }) diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index eab65f92e293..8823c8392282 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -144,9 +144,7 @@ extern "C" void LLVMRustPrintStatistics(RustStringRef OutBuf) { llvm::PrintStatistics(OS); } -extern "C" void LLVMRustOffloadMapper(LLVMModuleRef M, LLVMValueRef OldFn, - LLVMValueRef NewFn) { - llvm::Module *module = llvm::unwrap(M); +extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn) { llvm::Function *oldFn = llvm::unwrap(OldFn); llvm::Function *newFn = llvm::unwrap(NewFn); diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 808d9fbbc2ce..6c796b3a9c8c 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1744,7 +1744,8 @@ fn filter<'a>( src_hash, checksum_hash, start_pos: original_start_pos, - source_len, + normalized_source_len, + unnormalized_source_len, lines, multibyte_chars, normalized_pos, @@ -1804,7 +1805,8 @@ fn filter<'a>( src_hash, checksum_hash, stable_id, - source_len.to_u32(), + normalized_source_len.to_u32(), + unnormalized_source_len, self.cnum, lines, multibyte_chars, @@ -1817,9 +1819,9 @@ fn filter<'a>( translated (start_pos {:?} source_len {:?})", local_version.name, original_start_pos, - source_len, + normalized_source_len, local_version.start_pos, - local_version.source_len + local_version.normalized_source_len ); ImportedSourceFile { diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs index 8d4385a2fd32..ef8326fd038e 100644 --- a/compiler/rustc_middle/src/lib.rs +++ b/compiler/rustc_middle/src/lib.rs @@ -51,6 +51,7 @@ #![feature(negative_impls)] #![feature(never_type)] #![feature(ptr_alignment_type)] +#![feature(range_bounds_is_empty)] #![feature(rustc_attrs)] #![feature(sized_hierarchy)] #![feature(try_blocks)] diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index c0e443b5ead8..990ed8f48fb8 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -234,6 +234,17 @@ pub struct ScopeTree { pub backwards_incompatible_scope: UnordMap, } +/// Temporary lifetime information for expressions, used when lowering to MIR. +#[derive(Clone, Copy, Debug, HashStable)] +pub struct TempLifetime { + /// The scope in which a temporary should be dropped. If `None`, no drop is scheduled; this is + /// the case for lifetime-extended temporaries extended by a const/static item or const block. + pub temp_lifetime: Option, + /// If `Some(lt)`, indicates that the lifetime of this temporary will change to `lt` in a future edition. + /// If `None`, then no changes are expected, or lints are disabled. + pub backwards_incompatible: Option, +} + impl ScopeTree { pub fn record_scope_parent(&mut self, child: Scope, parent: Option) { debug!("{:?}.parent = {:?}", child, parent); @@ -332,16 +343,16 @@ pub fn default_temporary_scope(&self, inner: Scope) -> (Scope, Option) { /// Returns the scope when the temp created by `expr_id` will be cleaned up. /// It also emits a lint on potential backwards incompatible change to the temporary scope /// which is *for now* always shortening. - pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> (Option, Option) { + pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> TempLifetime { // Check for a designated extended temporary scope. if let Some(&s) = self.extended_temp_scopes.get(&expr_id) { debug!("temporary_scope({expr_id:?}) = {s:?} [custom]"); - return (s, None); + return TempLifetime { temp_lifetime: s, backwards_incompatible: None }; } // Otherwise, locate the innermost terminating scope. - let (scope, backward_incompatible) = + let (scope, backwards_incompatible) = self.default_temporary_scope(Scope { local_id: expr_id, data: ScopeData::Node }); - (Some(scope), backward_incompatible) + TempLifetime { temp_lifetime: Some(scope), backwards_incompatible } } } diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs index 8e603ce1b911..1cfe5219997b 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs @@ -19,9 +19,9 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use super::{ - AllocId, BadBytesAccess, CtfeProvenance, InterpErrorKind, InterpResult, Pointer, - PointerArithmetic, Provenance, ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, - UndefinedBehaviorInfo, UnsupportedOpInfo, interp_ok, read_target_uint, write_target_uint, + AllocId, BadBytesAccess, CtfeProvenance, InterpErrorKind, InterpResult, Pointer, Provenance, + ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, + interp_ok, read_target_uint, write_target_uint, }; use crate::ty; @@ -601,14 +601,13 @@ pub fn get_bytes_strip_provenance( })?; if !Prov::OFFSET_IS_ADDR && !self.provenance.range_empty(range, cx) { // Find the provenance. - let (offset, _prov) = self + let (prov_range, _prov) = self .provenance - .range_ptrs_get(range, cx) - .first() - .copied() + .get_range(range, cx) + .next() .expect("there must be provenance somewhere here"); - let start = offset.max(range.start); // the pointer might begin before `range`! - let end = (offset + cx.pointer_size()).min(range.end()); // the pointer might end after `range`! + let start = prov_range.start.max(range.start); // the pointer might begin before `range`! + let end = prov_range.end().min(range.end()); // the pointer might end after `range`! return Err(AllocError::ReadPointerAsInt(Some(BadBytesAccess { access: range, bad: AllocRange::from(start..end), @@ -630,7 +629,7 @@ pub fn get_bytes_unchecked_for_overwrite( range: AllocRange, ) -> &mut [u8] { self.mark_init(range, true); - self.provenance.clear(range, cx); + self.provenance.clear(range, &self.bytes, cx); &mut self.bytes[range.start.bytes_usize()..range.end().bytes_usize()] } @@ -643,7 +642,7 @@ pub fn get_bytes_unchecked_for_overwrite_ptr( range: AllocRange, ) -> *mut [u8] { self.mark_init(range, true); - self.provenance.clear(range, cx); + self.provenance.clear(range, &self.bytes, cx); assert!(range.end().bytes_usize() <= self.bytes.len()); // need to do our own bounds-check // Crucially, we go via `AllocBytes::as_mut_ptr`, not `AllocBytes::deref_mut`. @@ -711,57 +710,14 @@ pub fn read_scalar( if read_provenance { assert_eq!(range.size, cx.data_layout().pointer_size()); - // When reading data with provenance, the easy case is finding provenance exactly where we - // are reading, then we can put data and provenance back together and return that. - if let Some(prov) = self.provenance.get_ptr(range.start) { - // Now we can return the bits, with their appropriate provenance. + if let Some(prov) = self.provenance.read_ptr(range.start, cx)? { + // Assemble the bits with their provenance. let ptr = Pointer::new(prov, Size::from_bytes(bits)); - return Ok(Scalar::from_pointer(ptr, cx)); + Ok(Scalar::from_pointer(ptr, cx)) + } else { + // Return raw bits without provenance. + Ok(Scalar::from_uint(bits, range.size)) } - // The other easy case is total absence of provenance. - if self.provenance.range_empty(range, cx) { - return Ok(Scalar::from_uint(bits, range.size)); - } - // If we get here, we have to check per-byte provenance, and join them together. - let prov = 'prov: { - if !Prov::OFFSET_IS_ADDR { - // FIXME(#146291): We need to ensure that we don't mix different pointers with - // the same provenance. - return Err(AllocError::ReadPartialPointer(range.start)); - } - // Initialize with first fragment. Must have index 0. - let Some((mut joint_prov, 0)) = self.provenance.get_byte(range.start, cx) else { - break 'prov None; - }; - // Update with the remaining fragments. - for offset in Size::from_bytes(1)..range.size { - // Ensure there is provenance here and it has the right index. - let Some((frag_prov, frag_idx)) = - self.provenance.get_byte(range.start + offset, cx) - else { - break 'prov None; - }; - // Wildcard provenance is allowed to come with any index (this is needed - // for Miri's native-lib mode to work). - if u64::from(frag_idx) != offset.bytes() && Some(frag_prov) != Prov::WILDCARD { - break 'prov None; - } - // Merge this byte's provenance with the previous ones. - joint_prov = match Prov::join(joint_prov, frag_prov) { - Some(prov) => prov, - None => break 'prov None, - }; - } - break 'prov Some(joint_prov); - }; - if prov.is_none() && !Prov::OFFSET_IS_ADDR { - // There are some bytes with provenance here but overall the provenance does not add up. - // We need `OFFSET_IS_ADDR` to fall back to no-provenance here; without that option, we must error. - return Err(AllocError::ReadPartialPointer(range.start)); - } - // We can use this provenance. - let ptr = Pointer::new(prov, Size::from_bytes(bits)); - return Ok(Scalar::from_maybe_pointer(ptr, cx)); } else { // We are *not* reading a pointer. // If we can just ignore provenance or there is none, that's easy. @@ -816,7 +772,7 @@ pub fn write_scalar( /// Write "uninit" to the given memory range. pub fn write_uninit(&mut self, cx: &impl HasDataLayout, range: AllocRange) { self.mark_init(range, false); - self.provenance.clear(range, cx); + self.provenance.clear(range, &self.bytes, cx); } /// Mark all bytes in the given range as initialised and reset the provenance @@ -831,21 +787,28 @@ pub fn process_native_write(&mut self, cx: &impl HasDataLayout, range: Option bool { self.provenance.merge_bytes(cx) } + pub fn provenance_prepare_copy( + &self, + range: AllocRange, + cx: &impl HasDataLayout, + ) -> ProvenanceCopy { + self.provenance.prepare_copy(range, &self.bytes, cx) + } + /// Applies a previously prepared provenance copy. - /// The affected range, as defined in the parameters to `provenance().prepare_copy` is expected - /// to be clear of provenance. + /// The affected range is expected to be clear of provenance. /// /// This is dangerous to use as it can violate internal `Allocation` invariants! /// It only exists to support an efficient implementation of `mem_copy_repeatedly`. diff --git a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs index 67baf63bbfad..9d38f3ebb157 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs @@ -2,7 +2,7 @@ //! representation for the common case where PTR_SIZE consecutive bytes have the same provenance. use std::cmp; -use std::ops::Range; +use std::ops::{Range, RangeBounds}; use rustc_abi::{HasDataLayout, Size}; use rustc_data_structures::sorted_map::SortedMap; @@ -13,6 +13,23 @@ use super::{AllocRange, CtfeProvenance, Provenance, alloc_range}; use crate::mir::interpret::{AllocError, AllocResult}; +/// A pointer fragment represents one byte of a pointer. +/// If the bytes are re-assembled in their original order, the pointer can be used again. +/// Wildcard provenance is allowed to have index 0 everywhere. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(HashStable)] +pub struct PointerFrag { + /// The position of this fragment inside the pointer (in `0..8`). + pub idx: u8, + /// The provenance of the pointer this is a fragment of. + pub prov: Prov, + /// The raw bytes of the pointer this is a fragment of. + /// This is taken as a direct subslice of the raw allocation data, so we don't have to worry + /// about endianness. If the pointer size is less than 8, only the first N bytes of this are + /// ever non-zero. + pub bytes: [u8; 8], +} + /// Stores the provenance information of pointers stored in memory. #[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(HashStable)] @@ -21,10 +38,7 @@ pub struct ProvenanceMap { /// bytes. Two entries in this map are always at least a pointer size apart. ptrs: SortedMap, /// This stores byte-sized provenance fragments. - /// The `u8` indicates the position of this byte inside its original pointer. - /// If the bytes are re-assembled in their original order, the pointer can be used again. - /// Wildcard provenance is allowed to have index 0 everywhere. - bytes: Option>>, + bytes: Option>>>, } // These impls are generic over `Prov` since `CtfeProvenance` is only decodable/encodable @@ -49,7 +63,7 @@ pub fn new() -> Self { } /// The caller must guarantee that the given provenance list is already sorted - /// by address and contain no duplicates. + /// by offset and contain no duplicates. pub fn from_presorted_ptrs(r: Vec<(Size, Prov)>) -> Self { ProvenanceMap { ptrs: SortedMap::from_presorted_elements(r), bytes: None } } @@ -80,11 +94,7 @@ fn adjusted_range_ptrs(range: AllocRange, cx: &impl HasDataLayout) -> Range &[(Size, Prov)] { + fn range_ptrs_get(&self, range: AllocRange, cx: &impl HasDataLayout) -> &[(Size, Prov)] { self.ptrs.range(Self::adjusted_range_ptrs(range, cx)) } @@ -93,8 +103,14 @@ fn range_ptrs_is_empty(&self, range: AllocRange, cx: &impl HasDataLayout) -> boo self.ptrs.range_is_empty(Self::adjusted_range_ptrs(range, cx)) } + /// Check if there is ptr-sized provenance at the given index. + /// Does not mean anything for bytewise provenance! But can be useful as an optimization. + pub fn get_ptr(&self, offset: Size) -> Option { + self.ptrs.get(&offset).copied() + } + /// Returns all byte-wise provenance in the given range. - fn range_bytes_get(&self, range: AllocRange) -> &[(Size, (Prov, u8))] { + fn range_bytes_get(&self, range: AllocRange) -> &[(Size, PointerFrag)] { if let Some(bytes) = self.bytes.as_ref() { bytes.range(range.start..range.end()) } else { @@ -107,68 +123,126 @@ fn range_bytes_is_empty(&self, range: AllocRange) -> bool { self.bytes.as_ref().is_none_or(|bytes| bytes.range_is_empty(range.start..range.end())) } - /// Get the provenance of a single byte. - pub fn get_byte(&self, offset: Size, cx: &impl HasDataLayout) -> Option<(Prov, u8)> { - let prov = self.range_ptrs_get(alloc_range(offset, Size::from_bytes(1)), cx); - debug_assert!(prov.len() <= 1); - if let Some(entry) = prov.first() { - // If it overlaps with this byte, it is on this byte. - debug_assert!(self.bytes.as_ref().is_none_or(|b| !b.contains_key(&offset))); - Some((entry.1, (offset - entry.0).bytes() as u8)) - } else { - // Look up per-byte provenance. - self.bytes.as_ref().and_then(|b| b.get(&offset).copied()) - } + /// Get the provenance of a single byte. Must only be called if there is no + /// pointer-sized provenance here. + pub fn get_byte(&self, offset: Size, cx: &impl HasDataLayout) -> Option<&PointerFrag> { + debug_assert!(self.range_ptrs_is_empty(alloc_range(offset, Size::from_bytes(1)), cx)); + self.bytes.as_ref().and_then(|b| b.get(&offset)) } /// Gets the provenances of all bytes (including from pointers) in a range. pub fn get_range( &self, - cx: &impl HasDataLayout, range: AllocRange, - ) -> impl Iterator { - let ptr_provs = self.range_ptrs_get(range, cx).iter().map(|(_, p)| *p); - let byte_provs = self.range_bytes_get(range).iter().map(|(_, (p, _))| *p); + cx: &impl HasDataLayout, + ) -> impl Iterator { + let ptr_size = cx.data_layout().pointer_size(); + let ptr_provs = self + .range_ptrs_get(range, cx) + .iter() + .map(move |(offset, p)| (alloc_range(*offset, ptr_size), *p)); + let byte_provs = self + .range_bytes_get(range) + .iter() + .map(move |(offset, frag)| (alloc_range(*offset, Size::from_bytes(1)), frag.prov)); ptr_provs.chain(byte_provs) } /// Attempt to merge per-byte provenance back into ptr chunks, if the right fragments - /// sit next to each other. Return `false` is that is not possible due to partial pointers. + /// sit next to each other. Return `false` if that is not possible due to partial pointers. pub fn merge_bytes(&mut self, cx: &impl HasDataLayout) -> bool { let Some(bytes) = self.bytes.as_deref_mut() else { return true; }; - if !Prov::OFFSET_IS_ADDR { - // FIXME(#146291): We need to ensure that we don't mix different pointers with - // the same provenance. - return false; - } let ptr_size = cx.data_layout().pointer_size(); - while let Some((offset, (prov, _))) = bytes.iter().next().copied() { + while let Some((offset, first_frag)) = bytes.iter().next() { + let offset = *offset; // Check if this fragment starts a pointer. let range = offset..offset + ptr_size; let frags = bytes.range(range.clone()); if frags.len() != ptr_size.bytes_usize() { + // We can't merge this one, no point in trying to merge the rest. return false; } - for (idx, (_offset, (frag_prov, frag_idx))) in frags.iter().copied().enumerate() { - if frag_prov != prov || frag_idx != idx as u8 { + for (idx, (_offset, frag)) in frags.iter().enumerate() { + if !(frag.prov == first_frag.prov + && frag.bytes == first_frag.bytes + && frag.idx == idx as u8) + { return false; } } // Looks like a pointer! Move it over to the ptr provenance map. + self.ptrs.insert(offset, first_frag.prov); bytes.remove_range(range); - self.ptrs.insert(offset, prov); } // We managed to convert everything into whole pointers. self.bytes = None; true } - /// Check if there is ptr-sized provenance at the given index. - /// Does not mean anything for bytewise provenance! But can be useful as an optimization. - pub fn get_ptr(&self, offset: Size) -> Option { - self.ptrs.get(&offset).copied() + /// Try to read a pointer from the given location, possibly by loading from many per-byte + /// provenances. + pub fn read_ptr(&self, offset: Size, cx: &impl HasDataLayout) -> AllocResult> { + // If there is pointer-sized provenance exactly here, we can just return that. + if let Some(prov) = self.get_ptr(offset) { + return Ok(Some(prov)); + } + // The other easy case is total absence of provenance, that also always works. + let range = alloc_range(offset, cx.data_layout().pointer_size()); + let no_ptrs = self.range_ptrs_is_empty(range, cx); + if no_ptrs && self.range_bytes_is_empty(range) { + return Ok(None); + } + // If we get here, we have to check whether we can merge per-byte provenance. + let prov = 'prov: { + // If there is any ptr-sized provenance overlapping with this range, + // this is definitely mixing multiple pointers and we can bail. + if !no_ptrs { + break 'prov None; + } + // Scan all fragments, and ensure their indices, provenance, and bytes match. + // However, we have to ignore wildcard fragments for this (this is needed for Miri's + // native-lib mode). Therefore, we will only know the expected provenance and bytes + // once we find the first non-wildcard fragment. + let mut expected = None; + for idx in Size::ZERO..range.size { + // Ensure there is provenance here. + let Some(frag) = self.get_byte(offset + idx, cx) else { + break 'prov None; + }; + // If this is wildcard provenance, ignore this fragment. + if Some(frag.prov) == Prov::WILDCARD { + continue; + } + // For non-wildcard fragments, the index must match. + if u64::from(frag.idx) != idx.bytes() { + break 'prov None; + } + // If there are expectations registered, check them. + // If not, record this fragment as setting the expectations. + match expected { + Some(expected) => { + if (frag.prov, frag.bytes) != expected { + break 'prov None; + } + } + None => { + expected = Some((frag.prov, frag.bytes)); + } + } + } + // The final provenance is the expected one we found along the way, or wildcard if + // we didn't find any. + Some(expected.map(|(prov, _addr)| prov).or_else(|| Prov::WILDCARD).unwrap()) + }; + if prov.is_none() && !Prov::OFFSET_IS_ADDR { + // There are some bytes with provenance here but overall the provenance does not add up. + // We need `OFFSET_IS_ADDR` to fall back to no-provenance here; without that option, we + // must error. + return Err(AllocError::ReadPartialPointer(offset)); + } + Ok(prov) } /// Returns whether this allocation has provenance overlapping with the given range. @@ -182,8 +256,8 @@ pub fn range_empty(&self, range: AllocRange, cx: &impl HasDataLayout) -> bool { /// Yields all the provenances stored in this map. pub fn provenances(&self) -> impl Iterator { - let bytes = self.bytes.iter().flat_map(|b| b.values().map(|(p, _i)| p)); - self.ptrs.values().chain(bytes).copied() + let bytes = self.bytes.iter().flat_map(|b| b.values().map(|frag| frag.prov)); + self.ptrs.values().copied().chain(bytes) } pub fn insert_ptr(&mut self, offset: Size, prov: Prov, cx: &impl HasDataLayout) { @@ -191,9 +265,37 @@ pub fn insert_ptr(&mut self, offset: Size, prov: Prov, cx: &impl HasDataLayout) self.ptrs.insert(offset, prov); } + /// Returns an iterator that yields the fragments of this pointer whose absolute positions are + /// inside `pos_range`. + fn ptr_fragments( + pos_range: impl RangeBounds, + ptr_pos: Size, + prov: Prov, + data_bytes: &[u8], + ptr_size: Size, + ) -> impl Iterator)> { + if pos_range.is_empty() { + return either::Left(std::iter::empty()); + } + // Read ptr_size many bytes starting at ptr_pos. + let mut bytes = [0u8; 8]; + (&mut bytes[..ptr_size.bytes_usize()]) + .copy_from_slice(&data_bytes[ptr_pos.bytes_usize()..][..ptr_size.bytes_usize()]); + // Yield the fragments of this pointer. + either::Right( + (ptr_pos..ptr_pos + ptr_size).filter(move |pos| pos_range.contains(pos)).map( + move |pos| (pos, PointerFrag { idx: (pos - ptr_pos).bytes() as u8, bytes, prov }), + ), + ) + } + /// Removes all provenance inside the given range. - /// If there is provenance overlapping with the edges, might result in an error. - pub fn clear(&mut self, range: AllocRange, cx: &impl HasDataLayout) { + #[allow(irrefutable_let_patterns)] // these actually make the code more clear + pub fn clear(&mut self, range: AllocRange, data_bytes: &[u8], cx: &impl HasDataLayout) { + if range.size == Size::ZERO { + return; + } + let start = range.start; let end = range.end(); // Clear the bytewise part -- this is easy. @@ -201,46 +303,42 @@ pub fn clear(&mut self, range: AllocRange, cx: &impl HasDataLayout) { bytes.remove_range(start..end); } + // Find all provenance overlapping the given range. + let ptrs_range = Self::adjusted_range_ptrs(range, cx); + if self.ptrs.range_is_empty(ptrs_range.clone()) { + // No provenance in this range, we are done. This is the common case. + return; + } let pointer_size = cx.data_layout().pointer_size(); - // For the ptr-sized part, find the first (inclusive) and last (exclusive) byte of - // provenance that overlaps with the given range. - let (first, last) = { - // Find all provenance overlapping the given range. - if self.range_ptrs_is_empty(range, cx) { - // No provenance in this range, we are done. This is the common case. - return; - } - - // This redoes some of the work of `range_get_ptrs_is_empty`, but this path is much - // colder than the early return above, so it's worth it. - let provenance = self.range_ptrs_get(range, cx); - (provenance.first().unwrap().0, provenance.last().unwrap().0 + pointer_size) - }; + // This redoes some of the work of `range_is_empty`, but this path is much + // colder than the early return above, so it's worth it. + let ptrs = self.ptrs.range(ptrs_range.clone()); // We need to handle clearing the provenance from parts of a pointer. - if first < start { + if let &(first, prov) = ptrs.first().unwrap() + && first < start + { // Insert the remaining part in the bytewise provenance. - let prov = self.ptrs[&first]; let bytes = self.bytes.get_or_insert_with(Box::default); - for offset in first..start { - bytes.insert(offset, (prov, (offset - first).bytes() as u8)); + for (pos, frag) in Self::ptr_fragments(..start, first, prov, data_bytes, pointer_size) { + bytes.insert(pos, frag); } } - if last > end { - let begin_of_last = last - pointer_size; + if let &(last, prov) = ptrs.last().unwrap() + && last + pointer_size > end + { // Insert the remaining part in the bytewise provenance. - let prov = self.ptrs[&begin_of_last]; let bytes = self.bytes.get_or_insert_with(Box::default); - for offset in end..last { - bytes.insert(offset, (prov, (offset - begin_of_last).bytes() as u8)); + for (pos, frag) in Self::ptr_fragments(end.., last, prov, data_bytes, pointer_size) { + bytes.insert(pos, frag); } } // Forget all the provenance. // Since provenance do not overlap, we know that removing until `last` (exclusive) is fine, // i.e., this will not remove any other provenance just after the ones we care about. - self.ptrs.remove_range(first..last); + self.ptrs.remove_range(ptrs_range); } /// Overwrites all provenance in the given range with wildcard provenance. @@ -248,30 +346,25 @@ pub fn clear(&mut self, range: AllocRange, cx: &impl HasDataLayout) { /// bytewise on their remaining bytes. /// /// Provided for usage in Miri and panics otherwise. - pub fn write_wildcards(&mut self, cx: &impl HasDataLayout, range: AllocRange) { + pub fn write_wildcards( + &mut self, + cx: &impl HasDataLayout, + data_bytes: &[u8], + range: AllocRange, + ) { let wildcard = Prov::WILDCARD.unwrap(); + // Clear existing provenance in this range. + self.clear(range, data_bytes, cx); + + // Make everything in the range wildcards. let bytes = self.bytes.get_or_insert_with(Box::default); - - // Remove pointer provenances that overlap with the range, then readd the edge ones bytewise. - let ptr_range = Self::adjusted_range_ptrs(range, cx); - let ptrs = self.ptrs.range(ptr_range.clone()); - if let Some((offset, prov)) = ptrs.first().copied() { - for byte_ofs in offset..range.start { - bytes.insert(byte_ofs, (prov, (byte_ofs - offset).bytes() as u8)); - } - } - if let Some((offset, prov)) = ptrs.last().copied() { - for byte_ofs in range.end()..offset + cx.data_layout().pointer_size() { - bytes.insert(byte_ofs, (prov, (byte_ofs - offset).bytes() as u8)); - } - } - self.ptrs.remove_range(ptr_range); - - // Overwrite bytewise provenance. for offset in range.start..range.end() { - // The fragment index does not matter for wildcard provenance. - bytes.insert(offset, (wildcard, 0)); + // The fragment index and bytes do not matter for wildcard provenance. + bytes.insert( + offset, + PointerFrag { prov: wildcard, idx: Default::default(), bytes: Default::default() }, + ); } } } @@ -281,15 +374,16 @@ pub fn write_wildcards(&mut self, cx: &impl HasDataLayout, range: AllocRange) { /// Offsets are relative to the beginning of the copied range. pub struct ProvenanceCopy { ptrs: Box<[(Size, Prov)]>, - bytes: Box<[(Size, (Prov, u8))]>, + bytes: Box<[(Size, PointerFrag)]>, } impl ProvenanceMap { pub fn prepare_copy( &self, range: AllocRange, + data_bytes: &[u8], cx: &impl HasDataLayout, - ) -> AllocResult> { + ) -> ProvenanceCopy { let shift_offset = move |offset| offset - range.start; let ptr_size = cx.data_layout().pointer_size(); @@ -312,14 +406,15 @@ pub fn prepare_copy( let end_overlap = self.range_ptrs_get(alloc_range(range.end(), Size::ZERO), cx).first(); // We only need to go here if there is some overlap or some bytewise provenance. if begin_overlap.is_some() || end_overlap.is_some() || self.bytes.is_some() { - let mut bytes: Vec<(Size, (Prov, u8))> = Vec::new(); + let mut bytes: Vec<(Size, PointerFrag)> = Vec::new(); // First, if there is a part of a pointer at the start, add that. - if let Some(entry) = begin_overlap { - trace!("start overlapping entry: {entry:?}"); + if let Some(&(pos, prov)) = begin_overlap { // For really small copies, make sure we don't run off the end of the range. - let entry_end = cmp::min(entry.0 + ptr_size, range.end()); - for offset in range.start..entry_end { - bytes.push((shift_offset(offset), (entry.1, (offset - entry.0).bytes() as u8))); + let end = cmp::min(pos + ptr_size, range.end()); + for (pos, frag) in + Self::ptr_fragments(range.start..end, pos, prov, data_bytes, ptr_size) + { + bytes.push((shift_offset(pos), frag)); } } else { trace!("no start overlapping entry"); @@ -329,45 +424,35 @@ pub fn prepare_copy( bytes.extend( self.range_bytes_get(range) .iter() - .map(|&(offset, reloc)| (shift_offset(offset), reloc)), + .map(|(offset, frag)| (shift_offset(*offset), frag.clone())), ); // And finally possibly parts of a pointer at the end. - if let Some(entry) = end_overlap { - trace!("end overlapping entry: {entry:?}"); - // For really small copies, make sure we don't start before `range` does. - let entry_start = cmp::max(entry.0, range.start); - for offset in entry_start..range.end() { - if bytes.last().is_none_or(|bytes_entry| bytes_entry.0 < offset) { - // The last entry, if it exists, has a lower offset than us, so we - // can add it at the end and remain sorted. - bytes.push(( - shift_offset(offset), - (entry.1, (offset - entry.0).bytes() as u8), - )); - } else { - // There already is an entry for this offset in there! This can happen when the - // start and end range checks actually end up hitting the same pointer, so we - // already added this in the "pointer at the start" part above. - assert!(entry.0 <= range.start); - } + // We only have to go here if this is actually different than the begin_overlap. + if let Some(&(pos, prov)) = end_overlap + && begin_overlap.is_none_or(|(begin, _)| *begin != pos) + { + // If this was a really small copy, we'd have handled this in begin_overlap. + assert!(pos >= range.start); + for (pos, frag) in + Self::ptr_fragments(pos..range.end(), pos, prov, data_bytes, ptr_size) + { + let pos = shift_offset(pos); + // The last entry, if it exists, has a lower offset than us, so we + // can add it at the end and remain sorted. + debug_assert!(bytes.last().is_none_or(|bytes_entry| bytes_entry.0 < pos)); + bytes.push((pos, frag)); } } else { trace!("no end overlapping entry"); } trace!("byte provenances: {bytes:?}"); - if !bytes.is_empty() && !Prov::OFFSET_IS_ADDR { - // FIXME(#146291): We need to ensure that we don't mix different pointers with - // the same provenance. - return Err(AllocError::ReadPartialPointer(range.start)); - } - // And again a buffer for the new list on the target side. bytes_box = bytes.into_boxed_slice(); } - Ok(ProvenanceCopy { ptrs: ptrs_box, bytes: bytes_box }) + ProvenanceCopy { ptrs: ptrs_box, bytes: bytes_box } } /// Applies a provenance copy. @@ -381,8 +466,8 @@ pub fn apply_copy(&mut self, copy: ProvenanceCopy, range: AllocRange, repe let chunk_len = copy.ptrs.len() as u64; self.ptrs.insert_presorted((0..chunk_len * repeat).map(|i| { let chunk = i / chunk_len; - let (offset, reloc) = copy.ptrs[(i % chunk_len) as usize]; - (shift_offset(chunk, offset), reloc) + let (offset, prov) = copy.ptrs[(i % chunk_len) as usize]; + (shift_offset(chunk, offset), prov) })); } if !copy.bytes.is_empty() { @@ -390,8 +475,8 @@ pub fn apply_copy(&mut self, copy: ProvenanceCopy, range: AllocRange, repe self.bytes.get_or_insert_with(Box::default).insert_presorted( (0..chunk_len * repeat).map(|i| { let chunk = i / chunk_len; - let (offset, reloc) = copy.bytes[(i % chunk_len) as usize]; - (shift_offset(chunk, offset), reloc) + let (offset, frag) = ©.bytes[(i % chunk_len) as usize]; + (shift_offset(chunk, *offset), frag.clone()) }), ); } diff --git a/compiler/rustc_middle/src/mir/interpret/pointer.rs b/compiler/rustc_middle/src/mir/interpret/pointer.rs index 2aac8852b7e8..b6bcc87ff041 100644 --- a/compiler/rustc_middle/src/mir/interpret/pointer.rs +++ b/compiler/rustc_middle/src/mir/interpret/pointer.rs @@ -77,9 +77,6 @@ pub trait Provenance: Copy + PartialEq + fmt::Debug + 'static { /// Otherwise this function is best-effort (but must agree with `Machine::ptr_get_alloc`). /// (Identifying the offset in that allocation, however, is harder -- use `Memory::ptr_get_alloc` for that.) fn get_alloc_id(self) -> Option; - - /// Defines the 'join' of provenance: what happens when doing a pointer load and different bytes have different provenance. - fn join(left: Self, right: Self) -> Option; } /// The type of provenance in the compile-time interpreter. @@ -191,10 +188,6 @@ fn fmt(ptr: &Pointer, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn get_alloc_id(self) -> Option { Some(self.alloc_id()) } - - fn join(left: Self, right: Self) -> Option { - if left == right { Some(left) } else { None } - } } // We also need this impl so that one can debug-print `Pointer` @@ -223,10 +216,6 @@ fn fmt(ptr: &Pointer, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn get_alloc_id(self) -> Option { Some(self) } - - fn join(_left: Self, _right: Self) -> Option { - unreachable!() - } } /// Represents a pointer in the Miri engine. diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index e31b110ab265..82cab107819f 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1791,7 +1791,7 @@ pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( ascii.push('╼'); i += ptr_size; } - } else if let Some((prov, idx)) = alloc.provenance().get_byte(i, &tcx) { + } else if let Some(frag) = alloc.provenance().get_byte(i, &tcx) { // Memory with provenance must be defined assert!( alloc.init_mask().is_range_initialized(alloc_range(i, Size::from_bytes(1))).is_ok() @@ -1801,7 +1801,8 @@ pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( // Format is similar to "oversized" above. let j = i.bytes_usize(); let c = alloc.inspect_with_uninit_and_ptr_outside_interpreter(j..j + 1)[0]; - write!(w, "╾{c:02x}{prov:#?} (ptr fragment {idx})╼")?; + // FIXME: Find a way to print `frag.offset` that does not look terrible... + write!(w, "╾{c:02x}{prov:#?} (ptr fragment {idx})╼", prov = frag.prov, idx = frag.idx)?; i += Size::from_bytes(1); } else if alloc .init_mask() diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index e72ed78d07e6..4f38ef017f49 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -256,25 +256,15 @@ pub struct Expr<'tcx> { /// The type of this expression pub ty: Ty<'tcx>, - /// The lifetime of this expression if it should be spilled into a - /// temporary - pub temp_lifetime: TempLifetime, + /// The id of the HIR expression whose [temporary scope] should be used for this expression. + /// + /// [temporary scope]: https://doc.rust-lang.org/reference/destructors.html#temporary-scopes + pub temp_scope_id: hir::ItemLocalId, /// span of the expression in the source pub span: Span, } -/// Temporary lifetime information for THIR expressions -#[derive(Clone, Copy, Debug, HashStable)] -pub struct TempLifetime { - /// Lifetime for temporaries as expected. - /// This should be `None` in a constant context. - pub temp_lifetime: Option, - /// If `Some(lt)`, indicates that the lifetime of this temporary will change to `lt` in a future edition. - /// If `None`, then no changes are expected, or lints are disabled. - pub backwards_incompatible: Option, -} - #[derive(Clone, Debug, HashStable)] pub enum ExprKind<'tcx> { /// `Scope`s are used to explicitly mark destruction scopes, @@ -1127,7 +1117,7 @@ mod size_asserts { use super::*; // tidy-alphabetical-start static_assert_size!(Block, 48); - static_assert_size!(Expr<'_>, 72); + static_assert_size!(Expr<'_>, 64); static_assert_size!(ExprKind<'_>, 40); static_assert_size!(Pat<'_>, 64); static_assert_size!(PatKind<'_>, 48); diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index dcfa6c4db327..2ed506767397 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -45,7 +45,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( expr: &'thir Expr<'tcx>, ) { use ExprKind::*; - let Expr { kind, ty: _, temp_lifetime: _, span: _ } = expr; + let Expr { kind, ty: _, temp_scope_id: _, span: _ } = expr; match *kind { Scope { value, region_scope: _, lint_level: _ } => { visitor.visit_expr(&visitor.thir()[value]) diff --git a/compiler/rustc_mir_build/src/builder/expr/as_constant.rs b/compiler/rustc_mir_build/src/builder/expr/as_constant.rs index 0e0c7a7fa4f0..eb0546cd0e37 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_constant.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_constant.rs @@ -21,7 +21,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { pub(crate) fn as_constant(&mut self, expr: &Expr<'tcx>) -> ConstOperand<'tcx> { let this = self; let tcx = this.tcx; - let Expr { ty, temp_lifetime: _, span, ref kind } = *expr; + let Expr { ty, temp_scope_id: _, span, ref kind } = *expr; match kind { ExprKind::Scope { region_scope: _, lint_level: _, value } => { this.as_constant(&this.thir[*value]) @@ -46,7 +46,7 @@ pub(crate) fn as_constant_inner<'tcx>( push_cuta: impl FnMut(&Box>) -> Option, tcx: TyCtxt<'tcx>, ) -> ConstOperand<'tcx> { - let Expr { ty, temp_lifetime: _, span, ref kind } = *expr; + let Expr { ty, temp_scope_id: _, span, ref kind } = *expr; match *kind { ExprKind::Literal { lit, neg } => { let const_ = lit_to_mir_constant(tcx, LitToConstInput { lit: lit.node, ty, neg }); diff --git a/compiler/rustc_mir_build/src/builder/expr/as_operand.rs b/compiler/rustc_mir_build/src/builder/expr/as_operand.rs index 6a4222239904..39ddf0edf5d2 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_operand.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_operand.rs @@ -1,5 +1,6 @@ //! See docs in build/expr/mod.rs +use rustc_middle::middle::region::TempLifetime; use rustc_middle::mir::*; use rustc_middle::thir::*; use tracing::{debug, instrument}; diff --git a/compiler/rustc_mir_build/src/builder/expr/as_place.rs b/compiler/rustc_mir_build/src/builder/expr/as_place.rs index 1b143f37a585..6c5356228338 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_place.rs @@ -503,10 +503,9 @@ fn expr_as_place( block.and(place_builder) } ExprKind::ValueTypeAscription { source, ref user_ty, user_ty_span } => { - let source_expr = &this.thir[source]; - let temp = unpack!( - block = this.as_temp(block, source_expr.temp_lifetime, source, mutability) - ); + let temp_lifetime = + this.region_scope_tree.temporary_scope(this.thir[source].temp_scope_id); + let temp = unpack!(block = this.as_temp(block, temp_lifetime, source, mutability)); if let Some(user_ty) = user_ty { let ty_source_info = this.source_info(user_ty_span); let annotation_index = @@ -539,10 +538,9 @@ fn expr_as_place( block.and(place_builder.project(PlaceElem::UnwrapUnsafeBinder(expr.ty))) } ExprKind::ValueUnwrapUnsafeBinder { source } => { - let source_expr = &this.thir[source]; - let temp = unpack!( - block = this.as_temp(block, source_expr.temp_lifetime, source, mutability) - ); + let temp_lifetime = + this.region_scope_tree.temporary_scope(this.thir[source].temp_scope_id); + let temp = unpack!(block = this.as_temp(block, temp_lifetime, source, mutability)); block.and(PlaceBuilder::from(temp).project(PlaceElem::UnwrapUnsafeBinder(expr.ty))) } @@ -590,8 +588,8 @@ fn expr_as_place( | ExprKind::WrapUnsafeBinder { .. } => { // these are not places, so we need to make a temporary. debug_assert!(!matches!(Category::of(&expr.kind), Some(Category::Place))); - let temp = - unpack!(block = this.as_temp(block, expr.temp_lifetime, expr_id, mutability)); + let temp_lifetime = this.region_scope_tree.temporary_scope(expr.temp_scope_id); + let temp = unpack!(block = this.as_temp(block, temp_lifetime, expr_id, mutability)); block.and(PlaceBuilder::from(temp)) } } @@ -637,7 +635,10 @@ fn lower_index_expression( // Making this a *fresh* temporary means we do not have to worry about // the index changing later: Nothing will ever change this temporary. // The "retagging" transformation (for Stacked Borrows) relies on this. - let index_lifetime = self.thir[index].temp_lifetime; + // Using the enclosing temporary scope for the index ensures it will live past where this + // place is used. This lifetime may be larger than strictly necessary but it means we don't + // need to pass a scope for operands to `as_place`. + let index_lifetime = self.region_scope_tree.temporary_scope(self.thir[index].temp_scope_id); let idx = unpack!(block = self.as_temp(block, index_lifetime, index, Mutability::Not)); block = self.bounds_check(block, &base_place, idx, expr_span, source_info); diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index 546bfc8ea547..91814bca76f1 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -4,7 +4,7 @@ use rustc_hir::lang_items::LangItem; use rustc_index::{Idx, IndexVec}; use rustc_middle::bug; -use rustc_middle::middle::region; +use rustc_middle::middle::region::{self, TempLifetime}; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::*; use rustc_middle::thir::*; diff --git a/compiler/rustc_mir_build/src/builder/expr/as_temp.rs b/compiler/rustc_mir_build/src/builder/expr/as_temp.rs index b0ce3527d8b3..754ab0c0a16e 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_temp.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_temp.rs @@ -2,7 +2,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::HirId; -use rustc_middle::middle::region::{Scope, ScopeData}; +use rustc_middle::middle::region::{Scope, ScopeData, TempLifetime}; use rustc_middle::mir::*; use rustc_middle::thir::*; use tracing::{debug, instrument}; diff --git a/compiler/rustc_mir_build/src/builder/expr/stmt.rs b/compiler/rustc_mir_build/src/builder/expr/stmt.rs index 675beceea14a..db0d3a449712 100644 --- a/compiler/rustc_mir_build/src/builder/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/builder/expr/stmt.rs @@ -1,4 +1,4 @@ -use rustc_middle::middle::region; +use rustc_middle::middle::region::{self, TempLifetime}; use rustc_middle::mir::*; use rustc_middle::span_bug; use rustc_middle::thir::*; diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 96c58dc14e8c..03a4256add84 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -15,7 +15,7 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::{BindingMode, ByRef, LangItem, LetStmt, LocalSource, Node, Pinnedness}; -use rustc_middle::middle::region; +use rustc_middle::middle::region::{self, TempLifetime}; use rustc_middle::mir::*; use rustc_middle::thir::{self, *}; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, ValTree, ValTreeKind}; diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 9115c17f3752..b8547e288027 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -135,6 +135,10 @@ fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) { self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi); } + if !callee_sig.abi.supports_guaranteed_tail_call() { + self.report_unsupported_abi(expr.span, callee_sig.abi); + } + // FIXME(explicit_tail_calls): this currently fails for cases where opaques are used. // e.g. // ``` @@ -358,6 +362,16 @@ fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: E self.found_errors = Err(err); } + fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) { + let err = self + .tcx + .dcx() + .struct_span_err(sp, "ABI does not support guaranteed tail calls") + .with_note(format!("`become` is not supported for `extern {callee_abi}` functions")) + .emit(); + self.found_errors = Err(err); + } + fn report_signature_mismatch( &mut self, sp: Span, diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 6ac935d3901e..59efd805b30e 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -71,7 +71,7 @@ pub(super) fn mirror_expr_inner(&mut self, hir_expr: &'tcx hir::Expr<'tcx>) -> E // Finally, wrap this up in the expr's scope. expr = Expr { - temp_lifetime: expr.temp_lifetime, + temp_scope_id: expr_scope.local_id, ty: expr.ty, span: hir_expr.span, kind: ExprKind::Scope { @@ -93,7 +93,7 @@ fn apply_adjustment( adjustment: &Adjustment<'tcx>, mut span: Span, ) -> Expr<'tcx> { - let Expr { temp_lifetime, .. } = expr; + let Expr { temp_scope_id, .. } = expr; // Adjust the span from the block, to the last expression of the // block. This is a better span when returning a mutable reference @@ -152,7 +152,7 @@ fn apply_adjustment( Ty::new_fn_def(self.tcx, call_def_id, self.tcx.mk_args(&[expr.ty.into()])); expr = Expr { - temp_lifetime, + temp_scope_id, ty: Ty::new_ref(self.tcx, self.tcx.lifetimes.re_erased, expr.ty, deref.mutbl), span, kind: ExprKind::Borrow { @@ -199,13 +199,13 @@ fn apply_adjustment( variant_index: FIRST_VARIANT, name: FieldIdx::ZERO, }; - let arg = Expr { temp_lifetime, ty: pin_ty, span, kind: pointer_target }; + let arg = Expr { temp_scope_id, ty: pin_ty, span, kind: pointer_target }; let arg = self.thir.exprs.push(arg); // arg = *pointer let expr = ExprKind::Deref { arg }; let arg = self.thir.exprs.push(Expr { - temp_lifetime, + temp_scope_id, ty: ptr_target_ty, span, kind: expr, @@ -219,7 +219,7 @@ fn apply_adjustment( let new_pin_target = Ty::new_ref(self.tcx, self.tcx.lifetimes.re_erased, ptr_target_ty, mutbl); let expr = self.thir.exprs.push(Expr { - temp_lifetime, + temp_scope_id, ty: new_pin_target, span, kind: ExprKind::Borrow { borrow_kind, arg }, @@ -242,7 +242,7 @@ fn apply_adjustment( } }; - Expr { temp_lifetime, ty: adjustment.target, span, kind } + Expr { temp_scope_id, ty: adjustment.target, span, kind } } /// Lowers a cast expression. @@ -251,7 +251,7 @@ fn apply_adjustment( fn mirror_expr_cast( &mut self, source: &'tcx hir::Expr<'tcx>, - temp_lifetime: TempLifetime, + temp_scope_id: hir::ItemLocalId, span: Span, ) -> ExprKind<'tcx> { let tcx = self.tcx; @@ -309,7 +309,7 @@ fn mirror_expr_cast( ); } let kind = ExprKind::NonHirLiteral { lit, user_ty: None }; - let offset = self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind }); + let offset = self.thir.exprs.push(Expr { temp_scope_id, ty: discr_ty, span, kind }); let source = match discr_did { // in case we are offsetting from a computed discriminant @@ -317,9 +317,9 @@ fn mirror_expr_cast( Some(did) => { let kind = ExprKind::NamedConst { def_id: did, args, user_ty: None }; let lhs = - self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind }); + self.thir.exprs.push(Expr { temp_scope_id, ty: discr_ty, span, kind }); let bin = ExprKind::Binary { op: BinOp::Add, lhs, rhs: offset }; - self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind: bin }) + self.thir.exprs.push(Expr { temp_scope_id, ty: discr_ty, span, kind: bin }) } None => offset, }; @@ -336,8 +336,6 @@ fn mirror_expr_cast( fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { let tcx = self.tcx; let expr_ty = self.typeck_results.expr_ty(expr); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = match expr.kind { // Here comes the interesting stuff: @@ -372,7 +370,7 @@ fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> let arg_tys = args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, arg_tys), - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, span: expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(args) }, }; @@ -398,7 +396,7 @@ fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> } let value = &args[0]; return Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, ty: expr_ty, span: expr.span, kind: ExprKind::Box { value: self.mirror_expr(value) }, @@ -502,17 +500,15 @@ fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> expr: Some(arg), safety_mode: BlockSafety::Safe, }); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(arg_expr.hir_id.local_id); arg = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: arg_expr.hir_id.local_id, ty: arg_ty, span: arg_expr.span, kind: ExprKind::Block { block }, }); } let expr = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, ty, span: expr.span, kind: ExprKind::Borrow { borrow_kind: mutbl.to_borrow_kind(), arg }, @@ -995,12 +991,10 @@ fn local( } } else { let block_ty = self.typeck_results.node_type(body.hir_id); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(body.hir_id.local_id); let block = self.mirror_block(body); let body = self.thir.exprs.push(Expr { ty: block_ty, - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: body.hir_id.local_id, span: self.thir[block].span, kind: ExprKind::Block { block }, }); @@ -1022,17 +1016,13 @@ fn local( expr, cast_ty.hir_id, user_ty, ); - let cast = self.mirror_expr_cast( - source, - TempLifetime { temp_lifetime, backwards_incompatible }, - expr.span, - ); + let cast = self.mirror_expr_cast(source, expr.hir_id.local_id, expr.span); if let Some(user_ty) = user_ty { // NOTE: Creating a new Expr and wrapping a Cast inside of it may be // inefficient, revisit this when performance becomes an issue. let cast_expr = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, ty: expr_ty, span: expr.span, kind: cast, @@ -1091,12 +1081,7 @@ fn local( hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"), }; - Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - ty: expr_ty, - span: expr.span, - kind, - } + Expr { temp_scope_id: expr.hir_id.local_id, ty: expr_ty, span: expr.span, kind } } fn user_args_applied_to_res( @@ -1140,8 +1125,6 @@ fn method_callee( span: Span, overloaded_callee: Option>, ) -> Expr<'tcx> { - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let (ty, user_ty) = match overloaded_callee { Some(fn_def) => (fn_def, None), None => { @@ -1158,7 +1141,7 @@ fn method_callee( } }; Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, ty, span, kind: ExprKind::ZstLiteral { user_ty }, @@ -1235,8 +1218,6 @@ fn convert_path_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, res: Res) -> ExprKi Res::Def(DefKind::Static { .. }, id) => { // this is &raw for extern static or static mut, and & for other statics let ty = self.tcx.static_ptr_ty(id, self.typing_env); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = if self.tcx.is_thread_local_static(id) { ExprKind::ThreadLocalRef(id) } else { @@ -1246,7 +1227,7 @@ fn convert_path_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, res: Res) -> ExprKi ExprKind::Deref { arg: self.thir.exprs.push(Expr { ty, - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, span: expr.span, kind, }), @@ -1317,13 +1298,11 @@ fn overloaded_place( // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let fun = self.method_callee(expr, span, overloaded_callee); let fun = self.thir.exprs.push(fun); let fun_ty = self.thir[fun].ty; let ref_expr = self.thir.exprs.push(Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id: expr.hir_id.local_id, ty: ref_ty, span, kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span }, @@ -1338,8 +1317,7 @@ fn convert_captured_hir_place( closure_expr: &'tcx hir::Expr<'tcx>, place: HirPlace<'tcx>, ) -> Expr<'tcx> { - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); + let temp_scope_id = closure_expr.hir_id.local_id; let var_ty = place.base_ty; // The result of capture analysis in `rustc_hir_typeck/src/upvar.rs` represents a captured path @@ -1353,7 +1331,7 @@ fn convert_captured_hir_place( }; let mut captured_place_expr = Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id, ty: var_ty, span: closure_expr.span, kind: self.convert_var(var_hir_id), @@ -1381,12 +1359,8 @@ fn convert_captured_hir_place( } }; - captured_place_expr = Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, - ty: proj.ty, - span: closure_expr.span, - kind, - }; + captured_place_expr = + Expr { temp_scope_id, ty: proj.ty, span: closure_expr.span, kind }; } captured_place_expr @@ -1401,8 +1375,7 @@ fn capture_upvar( let upvar_capture = captured_place.info.capture_kind; let captured_place_expr = self.convert_captured_hir_place(closure_expr, captured_place.place.clone()); - let (temp_lifetime, backwards_incompatible) = - self.region_scope_tree.temporary_scope(closure_expr.hir_id.local_id); + let temp_scope_id = closure_expr.hir_id.local_id; match upvar_capture { ty::UpvarCapture::ByValue => captured_place_expr, @@ -1411,7 +1384,7 @@ fn capture_upvar( let expr_id = self.thir.exprs.push(captured_place_expr); Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id, ty: upvar_ty, span: closure_expr.span, kind: ExprKind::ByUse { expr: expr_id, span }, @@ -1428,7 +1401,7 @@ fn capture_upvar( } }; Expr { - temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible }, + temp_scope_id, ty: upvar_ty, span: closure_expr.span, kind: ExprKind::Borrow { diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index 65b05a0e6a98..d26dfac0c2ab 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -10,7 +10,6 @@ use rustc_hir::lang_items::LangItem; use rustc_hir::{self as hir, HirId, find_attr}; use rustc_middle::bug; -use rustc_middle::middle::region; use rustc_middle::thir::*; use rustc_middle::ty::{self, TyCtxt}; use tracing::instrument; @@ -60,7 +59,6 @@ struct ThirBuildCx<'tcx> { typing_env: ty::TypingEnv<'tcx>, - region_scope_tree: &'tcx region::ScopeTree, typeck_results: &'tcx ty::TypeckResults<'tcx>, /// False to indicate that adjustments should not be applied. Only used for `custom_mir` @@ -106,7 +104,6 @@ fn new(tcx: TyCtxt<'tcx>, def: LocalDefId) -> Self { // FIXME(#132279): We're in a body, we should use a typing // mode which reveals the opaque types defined by that body. typing_env: ty::TypingEnv::non_body_analysis(tcx, def), - region_scope_tree: tcx.region_scope_tree(def), typeck_results, body_owner: def.to_def_id(), apply_adjustments: diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index e0a5c18a2eed..655cd5f5dc95 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -183,10 +183,10 @@ fn print_stmt(&mut self, stmt_id: StmtId, depth_lvl: usize) { } fn print_expr(&mut self, expr: ExprId, depth_lvl: usize) { - let Expr { ty, temp_lifetime, span, kind } = &self.thir[expr]; + let Expr { ty, temp_scope_id, span, kind } = &self.thir[expr]; print_indented!(self, "Expr {", depth_lvl); print_indented!(self, format!("ty: {:?}", ty), depth_lvl + 1); - print_indented!(self, format!("temp_lifetime: {:?}", temp_lifetime), depth_lvl + 1); + print_indented!(self, format!("temp_scope_id: {:?}", temp_scope_id), depth_lvl + 1); print_indented!(self, format!("span: {:?}", span), depth_lvl + 1); print_indented!(self, "kind: ", depth_lvl + 1); self.print_expr_kind(kind, depth_lvl + 2); diff --git a/compiler/rustc_mir_transform/src/coverage/expansion.rs b/compiler/rustc_mir_transform/src/coverage/expansion.rs index 851bbaeed48e..827df27da52c 100644 --- a/compiler/rustc_mir_transform/src/coverage/expansion.rs +++ b/compiler/rustc_mir_transform/src/coverage/expansion.rs @@ -1,7 +1,12 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; +use rustc_middle::mir; use rustc_middle::mir::coverage::BasicCoverageBlock; use rustc_span::{ExpnId, ExpnKind, Span}; +use crate::coverage::from_mir; +use crate::coverage::graph::CoverageGraph; +use crate::coverage::hir_info::ExtractedHirInfo; + #[derive(Clone, Copy, Debug)] pub(crate) struct SpanWithBcb { pub(crate) span: Span, @@ -70,6 +75,10 @@ pub(crate) struct ExpnNode { pub(crate) spans: Vec, /// Expansions whose call-site is in this expansion. pub(crate) child_expn_ids: FxIndexSet, + + /// Hole spans belonging to this expansion, to be carved out from the + /// code spans during span refinement. + pub(crate) hole_spans: Vec, } impl ExpnNode { @@ -88,17 +97,27 @@ fn new(expn_id: ExpnId) -> Self { spans: vec![], child_expn_ids: FxIndexSet::default(), + + hole_spans: vec![], } } } -/// Given a collection of span/BCB pairs from potentially-different syntax contexts, +/// Extracts raw span/BCB pairs from potentially-different syntax contexts, and /// arranges them into an "expansion tree" based on their expansion call-sites. -pub(crate) fn build_expn_tree(spans: impl IntoIterator) -> ExpnTree { +pub(crate) fn build_expn_tree( + mir_body: &mir::Body<'_>, + hir_info: &ExtractedHirInfo, + graph: &CoverageGraph, +) -> ExpnTree { + let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph); + let mut nodes = FxIndexMap::default(); let new_node = |&expn_id: &ExpnId| ExpnNode::new(expn_id); - for span_with_bcb in spans { + for from_mir::RawSpanFromMir { raw_span, bcb } in raw_spans { + let span_with_bcb = SpanWithBcb { span: raw_span, bcb }; + // Create a node for this span's enclosing expansion, and add the span to it. let expn_id = span_with_bcb.span.ctxt().outer_expn(); let node = nodes.entry(expn_id).or_insert_with_key(new_node); @@ -123,5 +142,13 @@ pub(crate) fn build_expn_tree(spans: impl IntoIterator) -> E } } + // Associate each hole span (extracted from HIR) with its corresponding + // expansion tree node. + for &hole_span in &hir_info.hole_spans { + let expn_id = hole_span.ctxt().outer_expn(); + let Some(node) = nodes.get_mut(&expn_id) else { continue }; + node.hole_spans.push(hole_span); + } + ExpnTree { nodes } } diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/from_mir.rs similarity index 94% rename from compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs rename to compiler/rustc_mir_transform/src/coverage/from_mir.rs index c096f1e2632c..e1623f590a8e 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/from_mir.rs @@ -142,19 +142,3 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option { | TerminatorKind::InlineAsm { .. } => Some(terminator.source_info.span), } } - -#[derive(Debug)] -pub(crate) struct Hole { - pub(crate) span: Span, -} - -impl Hole { - pub(crate) fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool { - if !self.span.overlaps_or_adjacent(other.span) { - return false; - } - - self.span = self.span.to(other.span); - true - } -} diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index 8dbe564f5174..5347e1150e5d 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -5,6 +5,7 @@ use rustc_middle::mir::{self, BasicBlock, StatementKind}; use rustc_middle::ty::TyCtxt; +use crate::coverage::expansion; use crate::coverage::graph::CoverageGraph; use crate::coverage::hir_info::ExtractedHirInfo; use crate::coverage::spans::extract_refined_covspans; @@ -23,10 +24,12 @@ pub(crate) fn extract_mappings_from_mir<'tcx>( hir_info: &ExtractedHirInfo, graph: &CoverageGraph, ) -> ExtractedMappings { + let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph); + let mut mappings = vec![]; // Extract ordinary code mappings from MIR statement/terminator spans. - extract_refined_covspans(tcx, mir_body, hir_info, graph, &mut mappings); + extract_refined_covspans(tcx, hir_info, graph, &expn_tree, &mut mappings); extract_branch_mappings(mir_body, hir_info, graph, &mut mappings); diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 08c7d346009c..cc9d7c800a96 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -9,6 +9,7 @@ mod counters; mod expansion; +mod from_mir; mod graph; mod hir_info; mod mappings; diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index 325935ee8468..2a06b409e8c2 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -1,22 +1,18 @@ -use rustc_middle::mir; use rustc_middle::mir::coverage::{Mapping, MappingKind, START_BCB}; use rustc_middle::ty::TyCtxt; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DesugaringKind, ExpnId, ExpnKind, MacroKind, Span}; use tracing::instrument; -use crate::coverage::expansion::{self, ExpnTree, SpanWithBcb}; +use crate::coverage::expansion::{ExpnTree, SpanWithBcb}; use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph}; use crate::coverage::hir_info::ExtractedHirInfo; -use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir}; - -mod from_mir; pub(super) fn extract_refined_covspans<'tcx>( tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, hir_info: &ExtractedHirInfo, graph: &CoverageGraph, + expn_tree: &ExpnTree, mappings: &mut Vec, ) { if hir_info.is_async_fn { @@ -32,22 +28,32 @@ pub(super) fn extract_refined_covspans<'tcx>( let &ExtractedHirInfo { body_span, .. } = hir_info; - let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph); - // Use the raw spans to build a tree of expansions for this function. - let expn_tree = expansion::build_expn_tree( - raw_spans - .into_iter() - .map(|RawSpanFromMir { raw_span, bcb }| SpanWithBcb { span: raw_span, bcb }), - ); + // If there somehow isn't an expansion tree node corresponding to the + // body span, return now and don't create any mappings. + let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) else { return }; let mut covspans = vec![]; - let mut push_covspan = |covspan: Covspan| { + + for &SpanWithBcb { span, bcb } in &node.spans { + covspans.push(Covspan { span, bcb }); + } + + // For each expansion with its call-site in the body span, try to + // distill a corresponding covspan. + for &child_expn_id in &node.child_expn_ids { + if let Some(covspan) = single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id) + { + covspans.push(covspan); + } + } + + covspans.retain(|covspan: &Covspan| { let covspan_span = covspan.span; // Discard any spans not contained within the function body span. // Also discard any spans that fill the entire body, because they tend // to represent compiler-inserted code, e.g. implicitly returning `()`. if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) { - return; + return false; } // Each pushed covspan should have the same context as the body span. @@ -57,27 +63,11 @@ pub(super) fn extract_refined_covspans<'tcx>( false, "span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}" ); - return; + return false; } - covspans.push(covspan); - }; - - if let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) { - for &SpanWithBcb { span, bcb } in &node.spans { - push_covspan(Covspan { span, bcb }); - } - - // For each expansion with its call-site in the body span, try to - // distill a corresponding covspan. - for &child_expn_id in &node.child_expn_ids { - if let Some(covspan) = - single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id) - { - push_covspan(covspan); - } - } - } + true + }); // Only proceed if we found at least one usable span. if covspans.is_empty() { @@ -107,14 +97,8 @@ pub(super) fn extract_refined_covspans<'tcx>( covspans.dedup_by(|b, a| a.span.source_equal(b.span)); // Sort the holes, and merge overlapping/adjacent holes. - let mut holes = hir_info - .hole_spans - .iter() - .copied() - // Discard any holes that aren't directly visible within the body span. - .filter(|&hole_span| body_span.contains(hole_span) && body_span.eq_ctxt(hole_span)) - .map(|span| Hole { span }) - .collect::>(); + let mut holes = node.hole_spans.iter().copied().map(|span| Hole { span }).collect::>(); + holes.sort_by(|a, b| compare_spans(a.span, b.span)); holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b)); @@ -295,3 +279,19 @@ fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option { }) .ok()? } + +#[derive(Debug)] +struct Hole { + span: Span, +} + +impl Hole { + fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool { + if !self.span.overlaps_or_adjacent(other.span) { + return false; + } + + self.span = self.span.to(other.span); + true + } +} diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 4e601a6c5944..39b6fac4ebc0 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -88,17 +88,25 @@ fn current_query_job(self) -> Option { tls::with_related_context(self.tcx, |icx| icx.query) } - /// Returns a query map representing active query jobs. - /// It returns an incomplete map as an error if it fails - /// to take locks. + /// Returns a map of currently active query jobs. + /// + /// If `require_complete` is `true`, this function locks all shards of the + /// query results to produce a complete map, which always returns `Ok`. + /// Otherwise, it may return an incomplete map as an error if any shard + /// lock cannot be acquired. + /// + /// Prefer passing `false` to `require_complete` to avoid potential deadlocks, + /// especially when called from within a deadlock handler, unless a + /// complete map is needed and no deadlock is possible at this call site. fn collect_active_jobs( self, + require_complete: bool, ) -> Result>, QueryMap>> { let mut jobs = QueryMap::default(); let mut complete = true; - for collect in super::TRY_COLLECT_ACTIVE_JOBS.iter() { - if collect(self.tcx, &mut jobs).is_none() { + for collect in super::COLLECT_ACTIVE_JOBS.iter() { + if collect(self.tcx, &mut jobs, require_complete).is_none() { complete = false; } } @@ -163,11 +171,7 @@ fn start_query( } fn depth_limit_error(self, job: QueryJobId) { - // FIXME: `collect_active_jobs` expects no locks to be held, which doesn't hold for this call. - let query_map = match self.collect_active_jobs() { - Ok(query_map) => query_map, - Err(query_map) => query_map, - }; + let query_map = self.collect_active_jobs(true).expect("failed to collect active queries"); let (info, depth) = job.find_dep_kind_root(query_map); let suggested_limit = match self.recursion_limit() { @@ -731,19 +735,21 @@ fn restore(value: >>::Value) -> Self } } - pub(crate) fn try_collect_active_jobs<'tcx>( + pub(crate) fn collect_active_jobs<'tcx>( tcx: TyCtxt<'tcx>, qmap: &mut QueryMap>, + require_complete: bool, ) -> Option<()> { let make_query = |tcx, key| { let kind = rustc_middle::dep_graph::dep_kinds::$name; let name = stringify!($name); $crate::plumbing::create_query_frame(tcx, rustc_middle::query::descs::$name, key, kind, name) }; - let res = tcx.query_system.states.$name.try_collect_active_jobs( + let res = tcx.query_system.states.$name.collect_active_jobs( tcx, make_query, qmap, + require_complete, ); // this can be called during unwinding, and the function has a `try_`-prefix, so // don't `unwrap()` here, just manually check for `None` and do best-effort error @@ -814,10 +820,10 @@ pub fn dynamic_queries<'tcx>() -> DynamicQueries<'tcx> { // These arrays are used for iteration and can't be indexed by `DepKind`. - const TRY_COLLECT_ACTIVE_JOBS: &[ - for<'tcx> fn(TyCtxt<'tcx>, &mut QueryMap>) -> Option<()> + const COLLECT_ACTIVE_JOBS: &[ + for<'tcx> fn(TyCtxt<'tcx>, &mut QueryMap>, bool) -> Option<()> ] = - &[$(query_impl::$name::try_collect_active_jobs),*]; + &[$(query_impl::$name::collect_active_jobs),*]; const ALLOC_SELF_PROFILE_QUERY_STRINGS: &[ for<'tcx> fn(TyCtxt<'tcx>, &mut QueryKeyStringCache) diff --git a/compiler/rustc_query_system/src/ich/impls_syntax.rs b/compiler/rustc_query_system/src/ich/impls_syntax.rs index 044b97c2fea1..118229ffc990 100644 --- a/compiler/rustc_query_system/src/ich/impls_syntax.rs +++ b/compiler/rustc_query_system/src/ich/impls_syntax.rs @@ -54,7 +54,8 @@ fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHas checksum_hash: _, external_src: _, start_pos: _, - source_len: _, + normalized_source_len: _, + unnormalized_source_len: _, lines: _, ref multibyte_chars, ref normalized_pos, diff --git a/compiler/rustc_query_system/src/query/job.rs b/compiler/rustc_query_system/src/query/job.rs index fd1ea997ebe5..7d9b594d501f 100644 --- a/compiler/rustc_query_system/src/query/job.rs +++ b/compiler/rustc_query_system/src/query/job.rs @@ -616,7 +616,7 @@ pub fn print_query_stack( let mut count_total = 0; // Make use of a partial query map if we fail to take locks collecting active queries. - let query_map = match qcx.collect_active_jobs() { + let query_map = match qcx.collect_active_jobs(false) { Ok(query_map) => query_map, Err(query_map) => query_map, }; diff --git a/compiler/rustc_query_system/src/query/mod.rs b/compiler/rustc_query_system/src/query/mod.rs index 855769dacc3e..ce3456d532e6 100644 --- a/compiler/rustc_query_system/src/query/mod.rs +++ b/compiler/rustc_query_system/src/query/mod.rs @@ -161,7 +161,10 @@ pub trait QueryContext: HasDepContext { /// Get the query information from the TLS context. fn current_query_job(self) -> Option; - fn collect_active_jobs(self) -> Result, QueryMap>; + fn collect_active_jobs( + self, + require_complete: bool, + ) -> Result, QueryMap>; fn lift_query_info(self, info: &Self::QueryInfo) -> QueryStackFrameExtra; diff --git a/compiler/rustc_query_system/src/query/plumbing.rs b/compiler/rustc_query_system/src/query/plumbing.rs index e74de5edc42d..dea47c8fa787 100644 --- a/compiler/rustc_query_system/src/query/plumbing.rs +++ b/compiler/rustc_query_system/src/query/plumbing.rs @@ -7,10 +7,12 @@ use std::hash::Hash; use std::mem; +use hashbrown::HashTable; use hashbrown::hash_table::Entry; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::sharded::{self, Sharded}; use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_data_structures::sync::LockGuard; use rustc_data_structures::{outline, sync}; use rustc_errors::{Diag, FatalError, StashKey}; use rustc_span::{DUMMY_SP, Span}; @@ -63,22 +65,33 @@ pub fn all_inactive(&self) -> bool { self.active.lock_shards().all(|shard| shard.is_empty()) } - pub fn try_collect_active_jobs( + pub fn collect_active_jobs( &self, qcx: Qcx, make_query: fn(Qcx, K) -> QueryStackFrame, jobs: &mut QueryMap, + require_complete: bool, ) -> Option<()> { let mut active = Vec::new(); - // We use try_lock_shards here since we are called from the - // deadlock handler, and this shouldn't be locked. - for shard in self.active.try_lock_shards() { - for (k, v) in shard?.iter() { + let mut collect = |iter: LockGuard<'_, HashTable<(K, QueryResult)>>| { + for (k, v) in iter.iter() { if let QueryResult::Started(ref job) = *v { - active.push((*k, (*job).clone())); + active.push((*k, job.clone())); } } + }; + + if require_complete { + for shard in self.active.lock_shards() { + collect(shard); + } + } else { + // We use try_lock_shards here since we are called from the + // deadlock handler, and this shouldn't be locked. + for shard in self.active.try_lock_shards() { + collect(shard?); + } } // Call `make_query` while we're not holding a `self.active` lock as `make_query` may call @@ -271,7 +284,7 @@ fn cycle_error( { // Ensure there was no errors collecting all active jobs. // We need the complete map to ensure we find a cycle to break. - let query_map = qcx.collect_active_jobs().ok().expect("failed to collect active queries"); + let query_map = qcx.collect_active_jobs(false).ok().expect("failed to collect active queries"); let error = try_execute.find_cycle_in_stack(query_map, &qcx.current_query_job(), span); (mk_cycle(query, qcx, error.lift(qcx)), None) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index f3f0a74d03bc..8ecae07dea67 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -458,14 +458,11 @@ struct Flags: u8 { let mut result = Err(Determinacy::Determined); for derive in parent_scope.derives { let parent_scope = &ParentScope { derives: &[], ..*parent_scope }; - match this.reborrow().resolve_macro_path( + match this.reborrow().resolve_derive_macro_path( derive, - MacroKind::Derive, parent_scope, - true, force, ignore_import, - None, ) { Ok((Some(ext), _)) => { if ext.helper_attrs.contains(&ident.name) { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 107803c5c265..a92bc77b5b50 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -2916,9 +2916,9 @@ fn resolve_item(&mut self, item: &'ast Item) { } } - fn with_generic_param_rib<'c, F>( - &'c mut self, - params: &'c [GenericParam], + fn with_generic_param_rib( + &mut self, + params: &[GenericParam], kind: RibKind<'ra>, binder: NodeId, generics_kind: LifetimeBinderKind, diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 323b49e14a57..828759e2472f 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -396,14 +396,11 @@ fn resolve_derives( for (i, resolution) in entry.resolutions.iter_mut().enumerate() { if resolution.exts.is_none() { resolution.exts = Some( - match self.cm().resolve_macro_path( + match self.cm().resolve_derive_macro_path( &resolution.path, - MacroKind::Derive, &parent_scope, - true, force, None, - None, ) { Ok((Some(ext), _)) => { if !ext.helper_attrs.is_empty() { @@ -571,7 +568,6 @@ fn smart_resolve_macro_path( path, kind, parent_scope, - true, force, deleg_impl, invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)), @@ -713,26 +709,22 @@ fn smart_resolve_macro_path( Ok((ext, res)) } - pub(crate) fn resolve_macro_path<'r>( + pub(crate) fn resolve_derive_macro_path<'r>( self: CmResolver<'r, 'ra, 'tcx>, path: &ast::Path, - kind: MacroKind, parent_scope: &ParentScope<'ra>, - trace: bool, force: bool, ignore_import: Option>, - suggestion_span: Option, ) -> Result<(Option>, Res), Determinacy> { self.resolve_macro_or_delegation_path( path, - kind, + MacroKind::Derive, parent_scope, - trace, force, None, None, ignore_import, - suggestion_span, + None, ) } @@ -741,7 +733,6 @@ fn resolve_macro_or_delegation_path<'r>( ast_path: &ast::Path, kind: MacroKind, parent_scope: &ParentScope<'ra>, - trace: bool, force: bool, deleg_impl: Option, invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>, @@ -780,16 +771,14 @@ fn resolve_macro_or_delegation_path<'r>( PathResult::Module(..) => unreachable!(), }; - if trace { - self.multi_segment_macro_resolutions.borrow_mut(&self).push(( - path, - path_span, - kind, - *parent_scope, - res.ok(), - ns, - )); - } + self.multi_segment_macro_resolutions.borrow_mut(&self).push(( + path, + path_span, + kind, + *parent_scope, + res.ok(), + ns, + )); self.prohibit_imported_non_macro_attrs(None, res.ok(), path_span); res @@ -807,15 +796,13 @@ fn resolve_macro_or_delegation_path<'r>( return Err(Determinacy::Undetermined); } - if trace { - self.single_segment_macro_resolutions.borrow_mut(&self).push(( - path[0].ident, - kind, - *parent_scope, - binding.ok(), - suggestion_span, - )); - } + self.single_segment_macro_resolutions.borrow_mut(&self).push(( + path[0].ident, + kind, + *parent_scope, + binding.ok(), + suggestion_span, + )); let res = binding.map(|binding| binding.res()); self.prohibit_imported_non_macro_attrs(binding.ok(), res.ok(), path_span); diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index afd4564f1b6f..2e03ccb1aa1a 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -1723,8 +1723,10 @@ pub struct SourceFile { pub external_src: FreezeLock, /// The start position of this source in the `SourceMap`. pub start_pos: BytePos, - /// The byte length of this source. - pub source_len: RelativeBytePos, + /// The byte length of this source after normalization. + pub normalized_source_len: RelativeBytePos, + /// The byte length of this source before normalization. + pub unnormalized_source_len: u32, /// Locations of lines beginnings in the source code. pub lines: FreezeLock, /// Locations of multi-byte characters in the source code. @@ -1748,7 +1750,8 @@ fn clone(&self) -> Self { checksum_hash: self.checksum_hash, external_src: self.external_src.clone(), start_pos: self.start_pos, - source_len: self.source_len, + normalized_source_len: self.normalized_source_len, + unnormalized_source_len: self.unnormalized_source_len, lines: self.lines.clone(), multibyte_chars: self.multibyte_chars.clone(), normalized_pos: self.normalized_pos.clone(), @@ -1764,7 +1767,8 @@ fn encode(&self, s: &mut S) { self.src_hash.encode(s); self.checksum_hash.encode(s); // Do not encode `start_pos` as it's global state for this session. - self.source_len.encode(s); + self.normalized_source_len.encode(s); + self.unnormalized_source_len.encode(s); // We are always in `Lines` form by the time we reach here. assert!(self.lines.read().is_lines()); @@ -1837,7 +1841,8 @@ fn decode(d: &mut D) -> SourceFile { let name: FileName = Decodable::decode(d); let src_hash: SourceFileHash = Decodable::decode(d); let checksum_hash: Option = Decodable::decode(d); - let source_len: RelativeBytePos = Decodable::decode(d); + let normalized_source_len: RelativeBytePos = Decodable::decode(d); + let unnormalized_source_len = Decodable::decode(d); let lines = { let num_lines: u32 = Decodable::decode(d); if num_lines > 0 { @@ -1859,7 +1864,8 @@ fn decode(d: &mut D) -> SourceFile { SourceFile { name, start_pos: BytePos::from_u32(0), - source_len, + normalized_source_len, + unnormalized_source_len, src: None, src_hash, checksum_hash, @@ -1959,12 +1965,17 @@ pub fn new( SourceFileHash::new_in_memory(checksum_hash_kind, src.as_bytes()) } }); + // Capture the original source length before normalization. + let unnormalized_source_len = u32::try_from(src.len()).map_err(|_| OffsetOverflowError)?; + if unnormalized_source_len > Self::MAX_FILE_SIZE { + return Err(OffsetOverflowError); + } + let normalized_pos = normalize_src(&mut src); let stable_id = StableSourceFileId::from_filename_in_current_crate(&name); - let source_len = src.len(); - let source_len = u32::try_from(source_len).map_err(|_| OffsetOverflowError)?; - if source_len > Self::MAX_FILE_SIZE { + let normalized_source_len = u32::try_from(src.len()).map_err(|_| OffsetOverflowError)?; + if normalized_source_len > Self::MAX_FILE_SIZE { return Err(OffsetOverflowError); } @@ -1977,7 +1988,8 @@ pub fn new( checksum_hash, external_src: FreezeLock::frozen(ExternalSource::Unneeded), start_pos: BytePos::from_u32(0), - source_len: RelativeBytePos::from_u32(source_len), + normalized_source_len: RelativeBytePos::from_u32(normalized_source_len), + unnormalized_source_len, lines: FreezeLock::frozen(SourceFileLines::Lines(lines)), multibyte_chars, normalized_pos, @@ -2161,7 +2173,7 @@ pub fn relative_position(&self, pos: BytePos) -> RelativeBytePos { #[inline] pub fn end_position(&self) -> BytePos { - self.absolute_position(self.source_len) + self.absolute_position(self.normalized_source_len) } /// Finds the line containing the given position. The return value is the @@ -2197,7 +2209,7 @@ pub fn contains(&self, byte_pos: BytePos) -> bool { #[inline] pub fn is_empty(&self) -> bool { - self.source_len.to_u32() == 0 + self.normalized_source_len.to_u32() == 0 } /// Calculates the original byte position relative to the start of the file diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 166842e374b6..17de34c8436f 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -262,7 +262,7 @@ pub fn load_binary_file(&self, path: &Path) -> io::Result<(Arc<[u8]>, Span)> { bytes, Span::new( file.start_pos, - BytePos(file.start_pos.0 + file.source_len.0), + BytePos(file.start_pos.0 + file.normalized_source_len.0), SyntaxContext::root(), None, ), @@ -353,14 +353,15 @@ pub fn new_imported_source_file( src_hash: SourceFileHash, checksum_hash: Option, stable_id: StableSourceFileId, - source_len: u32, + normalized_source_len: u32, + unnormalized_source_len: u32, cnum: CrateNum, file_local_lines: FreezeLock, multibyte_chars: Vec, normalized_pos: Vec, metadata_index: u32, ) -> Arc { - let source_len = RelativeBytePos::from_u32(source_len); + let normalized_source_len = RelativeBytePos::from_u32(normalized_source_len); let source_file = SourceFile { name: filename, @@ -372,7 +373,8 @@ pub fn new_imported_source_file( metadata_index, }), start_pos: BytePos(0), - source_len, + normalized_source_len, + unnormalized_source_len, lines: file_local_lines, multibyte_chars, normalized_pos, @@ -566,7 +568,7 @@ pub fn span_to_source(&self, sp: Span, extract_source: F) -> Result end_index || end_index > source_len { return Err(SpanSnippetError::MalformedForSourcemap(MalformedSourceMapPositions { @@ -997,7 +999,7 @@ fn find_width_of_character_at_span(&self, sp: SpanData, forwards: bool) -> u32 { return 1; } - let source_len = local_begin.sf.source_len.to_usize(); + let source_len = local_begin.sf.normalized_source_len.to_usize(); debug!("source_len=`{:?}`", source_len); // Ensure indexes are also not malformed. if start_index > end_index || end_index > source_len - 1 { diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs index 589c2a363548..c919aacf6b5f 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -230,7 +230,8 @@ fn t10() { name, src_hash, checksum_hash, - source_len, + normalized_source_len, + unnormalized_source_len, lines, multibyte_chars, normalized_pos, @@ -243,7 +244,8 @@ fn t10() { src_hash, checksum_hash, stable_id, - source_len.to_u32(), + normalized_source_len.to_u32(), + unnormalized_source_len, CrateNum::ZERO, FreezeLock::new(lines.read().clone()), multibyte_chars, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a4d86e5df9a9..8ab818183306 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1259,6 +1259,7 @@ into_async_iter_into_iter, into_future, into_iter, + into_try_type, intra_doc_pointers, intrinsics, intrinsics_unaligned_volatile_load, @@ -2280,6 +2281,7 @@ try_from_fn, try_into, try_trait_v2, + try_trait_v2_residual, try_update, tt, tuple, diff --git a/compiler/rustc_span/src/tests.rs b/compiler/rustc_span/src/tests.rs index ed1db3446342..64c40e611625 100644 --- a/compiler/rustc_span/src/tests.rs +++ b/compiler/rustc_span/src/tests.rs @@ -103,3 +103,17 @@ fn test_trim() { assert_eq!(span(well_before, before).trim_start(other), None); } + +#[test] +fn test_unnormalized_source_length() { + let source = "\u{feff}hello\r\nferries\r\n".to_owned(); + let sf = SourceFile::new( + FileName::Anon(Hash64::ZERO), + source, + SourceFileHashAlgorithm::Sha256, + Some(SourceFileHashAlgorithm::Sha256), + ) + .unwrap(); + assert_eq!(sf.unnormalized_source_len, 19); + assert_eq!(sf.normalized_source_len.0, 14); +} diff --git a/compiler/rustc_target/src/spec/targets/aarch64_pc_windows_gnullvm.rs b/compiler/rustc_target/src/spec/targets/aarch64_pc_windows_gnullvm.rs index 9b7db11e2f29..7b3a234cd9e8 100644 --- a/compiler/rustc_target/src/spec/targets/aarch64_pc_windows_gnullvm.rs +++ b/compiler/rustc_target/src/spec/targets/aarch64_pc_windows_gnullvm.rs @@ -17,7 +17,7 @@ pub(crate) fn target() -> Target { metadata: TargetMetadata { description: Some("ARM64 MinGW (Windows 10+), LLVM ABI".into()), tier: Some(2), - host_tools: Some(false), + host_tools: Some(true), std: Some(true), }, pointer_width: 64, diff --git a/compiler/rustc_target/src/spec/targets/i586_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/targets/i586_unknown_linux_gnu.rs index f04e3c2c2a5b..508afd479352 100644 --- a/compiler/rustc_target/src/spec/targets/i586_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/targets/i586_unknown_linux_gnu.rs @@ -5,5 +5,11 @@ pub(crate) fn target() -> Target { base.rustc_abi = None; // overwrite the SSE2 ABI set by the base target base.cpu = "pentium".into(); base.llvm_target = "i586-unknown-linux-gnu".into(); + base.metadata = crate::spec::TargetMetadata { + description: Some("32-bit Linux (kernel 3.2, glibc 2.17+)".into()), + tier: Some(2), + host_tools: Some(false), + std: Some(true), + }; base } diff --git a/compiler/rustc_target/src/spec/targets/sparcv9_sun_solaris.rs b/compiler/rustc_target/src/spec/targets/sparcv9_sun_solaris.rs index 498d8182ad58..a6067f64c686 100644 --- a/compiler/rustc_target/src/spec/targets/sparcv9_sun_solaris.rs +++ b/compiler/rustc_target/src/spec/targets/sparcv9_sun_solaris.rs @@ -18,7 +18,7 @@ pub(crate) fn target() -> Target { metadata: TargetMetadata { description: Some("SPARC Solaris 11.4".into()), tier: Some(2), - host_tools: Some(false), + host_tools: Some(true), std: Some(true), }, pointer_width: 64, diff --git a/compiler/rustc_target/src/spec/targets/wasm32_wasip2.rs b/compiler/rustc_target/src/spec/targets/wasm32_wasip2.rs index e594d187e42b..b53bf3b7bd52 100644 --- a/compiler/rustc_target/src/spec/targets/wasm32_wasip2.rs +++ b/compiler/rustc_target/src/spec/targets/wasm32_wasip2.rs @@ -63,7 +63,7 @@ pub(crate) fn target() -> Target { llvm_target: "wasm32-wasip2".into(), metadata: TargetMetadata { description: Some("WebAssembly".into()), - tier: Some(3), + tier: Some(2), host_tools: Some(false), std: Some(true), }, diff --git a/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs b/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs index d417f3d48a4b..3fb1d11ef60d 100644 --- a/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs +++ b/compiler/rustc_target/src/spec/targets/wasm32_wasip3.rs @@ -15,6 +15,12 @@ pub(crate) fn target() -> Target { // and this may grow over time as more features are supported. let mut target = super::wasm32_wasip2::target(); target.llvm_target = "wasm32-wasip3".into(); + target.metadata = crate::spec::TargetMetadata { + description: Some("WebAssembly".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }; target.options.env = Env::P3; target } diff --git a/compiler/rustc_target/src/spec/targets/x86_64_pc_solaris.rs b/compiler/rustc_target/src/spec/targets/x86_64_pc_solaris.rs index 39ebe6243047..abcca352dfa3 100644 --- a/compiler/rustc_target/src/spec/targets/x86_64_pc_solaris.rs +++ b/compiler/rustc_target/src/spec/targets/x86_64_pc_solaris.rs @@ -20,7 +20,7 @@ pub(crate) fn target() -> Target { metadata: TargetMetadata { description: Some("64-bit Solaris 11.4".into()), tier: Some(2), - host_tools: Some(false), + host_tools: Some(true), std: Some(true), }, pointer_width: 64, diff --git a/compiler/rustc_target/src/spec/targets/x86_64_pc_windows_gnullvm.rs b/compiler/rustc_target/src/spec/targets/x86_64_pc_windows_gnullvm.rs index 28c9e6251255..0606d4508bad 100644 --- a/compiler/rustc_target/src/spec/targets/x86_64_pc_windows_gnullvm.rs +++ b/compiler/rustc_target/src/spec/targets/x86_64_pc_windows_gnullvm.rs @@ -14,7 +14,7 @@ pub(crate) fn target() -> Target { metadata: TargetMetadata { description: Some("64-bit x86 MinGW (Windows 10+), LLVM ABI".into()), tier: Some(2), - host_tools: Some(false), + host_tools: Some(true), std: Some(true), }, pointer_width: 64, diff --git a/library/Cargo.lock b/library/Cargo.lock index f06f57799c2f..b062b505cb24 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acbfca36652500c911ddb767ed433e3ed99b032b5d935be73c6923662db1d43" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", "rustc-std-workspace-alloc", @@ -49,9 +49,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" dependencies = [ "rustc-std-workspace-core", ] @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "dlmalloc" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa3a2dbee57b69fbb5dbe852fa9c0925697fb0c7fbcb1593e90e5ffaedf13d51" +checksum = "06cdfe340b16dd990c54cce79743613fa09fbb16774f33a77c9fd196f8f3fa30" dependencies = [ "cfg-if", "libc", @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.32.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93563d740bc9ef04104f9ed6f86f1e3275c2cdafb95664e26584b9ca807a8ffe" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ "rustc-std-workspace-alloc", "rustc-std-workspace-core", @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "vex-sdk" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89f74fce61d7a7ba1589da9634c6305a72befb7cc9150c1f872d87d8060f32b9" +checksum = "79e5fe15afde1305478b35e2cb717fff59f485428534cf49cfdbfa4723379bf6" dependencies = [ "rustc-std-workspace-core", ] @@ -422,12 +422,18 @@ dependencies = [ ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] @@ -436,10 +442,11 @@ version = "0.0.0" [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -452,57 +459,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" dependencies = [ "rustc-std-workspace-alloc", "rustc-std-workspace-core", diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index fde6b80a62fe..2b767ffe02be 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -725,9 +725,9 @@ pub fn into_inner(boxed: Self) -> T { #[unstable(feature = "box_take", issue = "147212")] pub fn take(boxed: Self) -> (T, Box, A>) { unsafe { - let (raw, alloc) = Box::into_raw_with_allocator(boxed); + let (raw, alloc) = Box::into_non_null_with_allocator(boxed); let value = raw.read(); - let uninit = Box::from_raw_in(raw.cast::>(), alloc); + let uninit = Box::from_non_null_in(raw.cast_uninit(), alloc); (value, uninit) } } diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs index ca5b46c9b0fd..766f4589177a 100644 --- a/library/alloc/src/collections/btree/map.rs +++ b/library/alloc/src/collections/btree/map.rs @@ -1434,7 +1434,8 @@ pub fn split_off(&mut self, key: &Q) -> Self /// /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating /// or the iteration short-circuits, then the remaining elements will be retained. - /// Use [`retain`] with a negated predicate if you do not need the returned iterator. + /// Use `extract_if().for_each(drop)` if you do not need the returned iterator, + /// or [`retain`] with a negated predicate if you also do not need to restrict the range. /// /// [`retain`]: BTreeMap::retain /// @@ -1945,7 +1946,8 @@ fn default() -> Self { /// An iterator produced by calling `extract_if` on BTreeMap. #[stable(feature = "btree_extract_if", since = "1.91.0")] -#[must_use = "iterators are lazy and do nothing unless consumed"] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `retain` or `extract_if().for_each(drop)` to remove and discard elements"] pub struct ExtractIf< 'a, K, diff --git a/library/alloc/src/collections/btree/set.rs b/library/alloc/src/collections/btree/set.rs index cb3e14252f8a..28d26699d7d2 100644 --- a/library/alloc/src/collections/btree/set.rs +++ b/library/alloc/src/collections/btree/set.rs @@ -1189,7 +1189,8 @@ pub fn split_off(&mut self, value: &Q) -> Self /// /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating /// or the iteration short-circuits, then the remaining elements will be retained. - /// Use [`retain`] with a negated predicate if you do not need the returned iterator. + /// Use `extract_if().for_each(drop)` if you do not need the returned iterator, + /// or [`retain`] with a negated predicate if you also do not need to restrict the range. /// /// [`retain`]: BTreeSet::retain /// # Examples @@ -1547,7 +1548,8 @@ fn into_iter(self) -> Iter<'a, T> { /// An iterator produced by calling `extract_if` on BTreeSet. #[stable(feature = "btree_extract_if", since = "1.91.0")] -#[must_use = "iterators are lazy and do nothing unless consumed"] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `retain` or `extract_if().for_each(drop)` to remove and discard elements"] pub struct ExtractIf< 'a, T, diff --git a/library/alloc/src/collections/linked_list.rs b/library/alloc/src/collections/linked_list.rs index 31dfe73fc799..8bc0e08a4b26 100644 --- a/library/alloc/src/collections/linked_list.rs +++ b/library/alloc/src/collections/linked_list.rs @@ -1943,7 +1943,8 @@ pub fn back_mut(&mut self) -> Option<&mut T> { /// An iterator produced by calling `extract_if` on LinkedList. #[stable(feature = "extract_if", since = "1.87.0")] -#[must_use = "iterators are lazy and do nothing unless consumed"] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `extract_if().for_each(drop)` to remove and discard elements"] pub struct ExtractIf< 'a, T: 'a, diff --git a/library/alloc/src/collections/vec_deque/extract_if.rs b/library/alloc/src/collections/vec_deque/extract_if.rs index bed7d46482cf..437f0d6dd5eb 100644 --- a/library/alloc/src/collections/vec_deque/extract_if.rs +++ b/library/alloc/src/collections/vec_deque/extract_if.rs @@ -21,7 +21,8 @@ /// let iter: ExtractIf<'_, _, _> = v.extract_if(.., |x| *x % 2 == 0); /// ``` #[unstable(feature = "vec_deque_extract_if", issue = "147750")] -#[must_use = "iterators are lazy and do nothing unless consumed"] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `retain_mut` or `extract_if().for_each(drop)` to remove and discard elements"] pub struct ExtractIf< 'a, T, diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 78930364a926..52e079d3ae8e 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -676,7 +676,8 @@ unsafe fn handle_capacity_increase(&mut self, old_capacity: usize) { /// /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating /// or the iteration short-circuits, then the remaining elements will be retained. - /// Use [`retain_mut`] with a negated predicate if you do not need the returned iterator. + /// Use `extract_if().for_each(drop)` if you do not need the returned iterator, + /// or [`retain_mut`] with a negated predicate if you also do not need to restrict the range. /// /// [`retain_mut`]: VecDeque::retain_mut /// diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 666ae27fb863..979e873bb2a3 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -116,6 +116,7 @@ #![feature(exact_size_is_empty)] #![feature(extend_one)] #![feature(extend_one_unchecked)] +#![feature(fmt_arguments_from_str)] #![feature(fmt_internals)] #![feature(fn_traits)] #![feature(formatting_options)] @@ -146,6 +147,7 @@ #![feature(std_internals)] #![feature(str_internals)] #![feature(temporary_niche_types)] +#![feature(transmutability)] #![feature(trivial_clone)] #![feature(trusted_fused)] #![feature(trusted_len)] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 31743b0e35b2..4a2689e01ff1 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -265,18 +265,11 @@ /// You can look at these with the [`as_ptr`], [`len`], and [`capacity`] /// methods: /// -// FIXME Update this when vec_into_raw_parts is stabilized /// ``` -/// use std::mem; -/// /// let story = String::from("Once upon a time..."); /// -/// // Prevent automatically dropping the String's data -/// let mut story = mem::ManuallyDrop::new(story); -/// -/// let ptr = story.as_mut_ptr(); -/// let len = story.len(); -/// let capacity = story.capacity(); +/// // Deconstruct the String into parts. +/// let (ptr, len, capacity) = story.into_raw_parts(); /// /// // story has nineteen bytes /// assert_eq!(19, len); @@ -932,7 +925,6 @@ pub fn from_utf16be_lossy(v: &[u8]) -> String { /// # Examples /// /// ``` - /// #![feature(vec_into_raw_parts)] /// let s = String::from("hello"); /// /// let (ptr, len, cap) = s.into_raw_parts(); @@ -941,7 +933,7 @@ pub fn from_utf16be_lossy(v: &[u8]) -> String { /// assert_eq!(rebuilt, "hello"); /// ``` #[must_use = "losing the pointer will leak memory"] - #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")] + #[stable(feature = "vec_into_raw_parts", since = "CURRENT_RUSTC_VERSION")] pub fn into_raw_parts(self) -> (*mut u8, usize, usize) { self.vec.into_raw_parts() } @@ -970,19 +962,12 @@ pub fn into_raw_parts(self) -> (*mut u8, usize, usize) { /// /// # Examples /// - // FIXME Update this when vec_into_raw_parts is stabilized /// ``` - /// use std::mem; - /// /// unsafe { /// let s = String::from("hello"); /// - /// // Prevent automatically dropping the String's data - /// let mut s = mem::ManuallyDrop::new(s); - /// - /// let ptr = s.as_mut_ptr(); - /// let len = s.len(); - /// let capacity = s.capacity(); + /// // Deconstruct the String into parts. + /// let (ptr, len, capacity) = s.into_raw_parts(); /// /// let s = String::from_raw_parts(ptr, len, capacity); /// diff --git a/library/alloc/src/vec/extract_if.rs b/library/alloc/src/vec/extract_if.rs index cb9e14f554d4..014219f8d461 100644 --- a/library/alloc/src/vec/extract_if.rs +++ b/library/alloc/src/vec/extract_if.rs @@ -16,7 +16,8 @@ /// let iter: std::vec::ExtractIf<'_, _, _> = v.extract_if(.., |x| *x % 2 == 0); /// ``` #[stable(feature = "extract_if", since = "1.87.0")] -#[must_use = "iterators are lazy and do nothing unless consumed"] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `retain_mut` or `extract_if().for_each(drop)` to remove and discard elements"] pub struct ExtractIf< 'a, T, diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 43a68ff20373..13d38d3c9609 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -82,7 +82,7 @@ #[cfg(not(no_global_oom_handling))] use core::iter; use core::marker::PhantomData; -use core::mem::{self, ManuallyDrop, MaybeUninit, SizedTypeProperties}; +use core::mem::{self, Assume, ManuallyDrop, MaybeUninit, SizedTypeProperties, TransmuteFrom}; use core::ops::{self, Index, IndexMut, Range, RangeBounds}; use core::ptr::{self, NonNull}; use core::slice::{self, SliceIndex}; @@ -592,21 +592,13 @@ pub fn try_with_capacity(capacity: usize) -> Result { /// /// # Examples /// - // FIXME Update this when vec_into_raw_parts is stabilized /// ``` /// use std::ptr; - /// use std::mem; /// /// let v = vec![1, 2, 3]; /// - /// // Prevent running `v`'s destructor so we are in complete control - /// // of the allocation. - /// let mut v = mem::ManuallyDrop::new(v); - /// - /// // Pull out the various important pieces of information about `v` - /// let p = v.as_mut_ptr(); - /// let len = v.len(); - /// let cap = v.capacity(); + /// // Deconstruct the vector into parts. + /// let (p, len, cap) = v.into_raw_parts(); /// /// unsafe { /// // Overwrite memory with 4, 5, 6 @@ -700,23 +692,13 @@ pub unsafe fn from_raw_parts(ptr: *mut T, length: usize, capacity: usize) -> Sel /// /// # Examples /// - // FIXME Update this when vec_into_raw_parts is stabilized /// ``` /// #![feature(box_vec_non_null)] /// - /// use std::ptr::NonNull; - /// use std::mem; - /// /// let v = vec![1, 2, 3]; /// - /// // Prevent running `v`'s destructor so we are in complete control - /// // of the allocation. - /// let mut v = mem::ManuallyDrop::new(v); - /// - /// // Pull out the various important pieces of information about `v` - /// let p = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) }; - /// let len = v.len(); - /// let cap = v.capacity(); + /// // Deconstruct the vector into parts. + /// let (p, len, cap) = v.into_parts(); /// /// unsafe { /// // Overwrite memory with 4, 5, 6 @@ -783,7 +765,6 @@ pub unsafe fn from_parts(ptr: NonNull, length: usize, capacity: usize) -> Sel /// # Examples /// /// ``` - /// #![feature(vec_into_raw_parts)] /// let v: Vec = vec![-1, 0, 1]; /// /// let (ptr, len, cap) = v.into_raw_parts(); @@ -798,7 +779,7 @@ pub unsafe fn from_parts(ptr: NonNull, length: usize, capacity: usize) -> Sel /// assert_eq!(rebuilt, [4294967295, 0, 1]); /// ``` #[must_use = "losing the pointer will leak memory"] - #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")] + #[stable(feature = "vec_into_raw_parts", since = "CURRENT_RUSTC_VERSION")] pub fn into_raw_parts(self) -> (*mut T, usize, usize) { let mut me = ManuallyDrop::new(self); (me.as_mut_ptr(), me.len(), me.capacity()) @@ -823,7 +804,7 @@ pub fn into_raw_parts(self) -> (*mut T, usize, usize) { /// # Examples /// /// ``` - /// #![feature(vec_into_raw_parts, box_vec_non_null)] + /// #![feature(box_vec_non_null)] /// /// let v: Vec = vec![-1, 0, 1]; /// @@ -840,7 +821,6 @@ pub fn into_raw_parts(self) -> (*mut T, usize, usize) { /// ``` #[must_use = "losing the pointer will leak memory"] #[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")] - // #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")] pub fn into_parts(self) -> (NonNull, usize, usize) { let (ptr, len, capacity) = self.into_raw_parts(); // SAFETY: A `Vec` always has a non-null pointer. @@ -996,29 +976,20 @@ pub fn try_with_capacity_in(capacity: usize, alloc: A) -> Result, length: usize, capacity: usize, all /// # Examples /// /// ``` - /// #![feature(allocator_api, vec_into_raw_parts)] + /// #![feature(allocator_api)] /// /// use std::alloc::System; /// @@ -1228,7 +1188,6 @@ pub unsafe fn from_parts_in(ptr: NonNull, length: usize, capacity: usize, all /// ``` #[must_use = "losing the pointer will leak memory"] #[unstable(feature = "allocator_api", issue = "32838")] - // #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")] pub fn into_raw_parts_with_alloc(self) -> (*mut T, usize, usize, A) { let mut me = ManuallyDrop::new(self); let len = me.len(); @@ -1256,7 +1215,7 @@ pub fn into_raw_parts_with_alloc(self) -> (*mut T, usize, usize, A) { /// # Examples /// /// ``` - /// #![feature(allocator_api, vec_into_raw_parts, box_vec_non_null)] + /// #![feature(allocator_api, box_vec_non_null)] /// /// use std::alloc::System; /// @@ -1279,7 +1238,6 @@ pub fn into_raw_parts_with_alloc(self) -> (*mut T, usize, usize, A) { #[must_use = "losing the pointer will leak memory"] #[unstable(feature = "allocator_api", issue = "32838")] // #[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")] - // #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")] pub fn into_parts_with_alloc(self) -> (NonNull, usize, usize, A) { let (ptr, len, capacity, alloc) = self.into_raw_parts_with_alloc(); // SAFETY: A `Vec` always has a non-null pointer. @@ -3243,6 +3201,92 @@ unsafe fn split_at_spare_mut_with_len( // - `cap / N` fits the size of the allocated memory after shrinking unsafe { Vec::from_raw_parts_in(ptr.cast(), len / N, cap / N, alloc) } } + + /// This clears out this `Vec` and recycles the allocation into a new `Vec`. + /// The item type of the resulting `Vec` needs to have the same size and + /// alignment as the item type of the original `Vec`. + /// + /// # Examples + /// + /// ``` + /// #![feature(vec_recycle, transmutability)] + /// let a: Vec = vec![0; 100]; + /// let capacity = a.capacity(); + /// let addr = a.as_ptr().addr(); + /// let b: Vec = a.recycle(); + /// assert_eq!(b.len(), 0); + /// assert_eq!(b.capacity(), capacity); + /// assert_eq!(b.as_ptr().addr(), addr); + /// ``` + /// + /// The `Recyclable` bound prevents this method from being called when `T` and `U` have different sizes; e.g.: + /// + /// ```compile_fail,E0277 + /// #![feature(vec_recycle, transmutability)] + /// let vec: Vec<[u8; 2]> = Vec::new(); + /// let _: Vec<[u8; 1]> = vec.recycle(); + /// ``` + /// ...or different alignments: + /// + /// ```compile_fail,E0277 + /// #![feature(vec_recycle, transmutability)] + /// let vec: Vec<[u16; 0]> = Vec::new(); + /// let _: Vec<[u8; 0]> = vec.recycle(); + /// ``` + /// + /// However, due to temporary implementation limitations of `Recyclable`, + /// this method is not yet callable when `T` or `U` are slices, trait objects, + /// or other exotic types; e.g.: + /// + /// ```compile_fail,E0277 + /// #![feature(vec_recycle, transmutability)] + /// # let inputs = ["a b c", "d e f"]; + /// # fn process(_: &[&str]) {} + /// let mut storage: Vec<&[&str]> = Vec::new(); + /// + /// for input in inputs { + /// let mut buffer: Vec<&str> = storage.recycle(); + /// buffer.extend(input.split(" ")); + /// process(&buffer); + /// storage = buffer.recycle(); + /// } + /// ``` + #[unstable(feature = "vec_recycle", issue = "148227")] + #[expect(private_bounds)] + pub fn recycle(mut self) -> Vec + where + U: Recyclable, + { + self.clear(); + const { + // FIXME(const-hack, 146097): compare `Layout`s + assert!(size_of::() == size_of::()); + assert!(align_of::() == align_of::()); + }; + let (ptr, length, capacity, alloc) = self.into_parts_with_alloc(); + debug_assert_eq!(length, 0); + // SAFETY: + // - `ptr` and `alloc` were just returned from `self.into_raw_parts_with_alloc()` + // - `T` & `U` have the same layout, so `capacity` does not need to be changed and we can safely use `alloc.dealloc` later + // - the original vector was cleared, so there is no problem with "transmuting" the stored values + unsafe { Vec::from_parts_in(ptr.cast::(), length, capacity, alloc) } + } +} + +/// Denotes that an allocation of `From` can be recycled into an allocation of `Self`. +/// +/// # Safety +/// +/// `Self` is `Recyclable` if `Layout::new::() == Layout::new::()`. +unsafe trait Recyclable: Sized {} + +#[unstable_feature_bound(transmutability)] +// SAFETY: enforced by `TransmuteFrom` +unsafe impl Recyclable for To +where + for<'a> &'a MaybeUninit: TransmuteFrom<&'a MaybeUninit, { Assume::SAFETY }>, + for<'a> &'a MaybeUninit: TransmuteFrom<&'a MaybeUninit, { Assume::SAFETY }>, +{ } impl Vec { @@ -3889,7 +3933,8 @@ pub fn splice(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoI /// /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating /// or the iteration short-circuits, then the remaining elements will be retained. - /// Use [`retain_mut`] with a negated predicate if you do not need the returned iterator. + /// Use `extract_if().for_each(drop)` if you do not need the returned iterator, + /// or [`retain_mut`] with a negated predicate if you also do not need to restrict the range. /// /// [`retain_mut`]: Vec::retain_mut /// diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 09d9b160700c..9a35ed07b89a 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -15,18 +15,18 @@ // actually reference libstd or liballoc in intra-doc links. so, the best we can do is remove the // links to `CString` and `String` for now until a solution is developed -/// Representation of a borrowed C string. +/// A dynamically-sized view of a C string. /// -/// This type represents a borrowed reference to a nul-terminated +/// The type `&CStr` represents a reference to a borrowed nul-terminated /// array of bytes. It can be constructed safely from a &[[u8]] /// slice, or unsafely from a raw `*const c_char`. It can be expressed as a /// literal in the form `c"Hello world"`. /// -/// The `CStr` can then be converted to a Rust &[str] by performing +/// The `&CStr` can then be converted to a Rust &[str] by performing /// UTF-8 validation, or into an owned `CString`. /// /// `&CStr` is to `CString` as &[str] is to `String`: the former -/// in each pair are borrowed references; the latter are owned +/// in each pair are borrowing references; the latter are owned /// strings. /// /// Note that this structure does **not** have a guaranteed layout (the `repr(transparent)` diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index e00e48bcfeb7..8ff64109b5cf 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -734,17 +734,6 @@ pub unsafe fn new( unsafe { Arguments { template: mem::transmute(template), args: mem::transmute(args) } } } - #[inline] - pub const fn from_str(s: &'static str) -> Arguments<'a> { - // SAFETY: This is the "static str" representation of fmt::Arguments; see above. - unsafe { - Arguments { - template: mem::transmute(s.as_ptr()), - args: mem::transmute(s.len() << 1 | 1), - } - } - } - // Same as `from_str`, but not const. // Used by format_args!() expansion when arguments are inlined, // e.g. format_args!("{}", 123), which is not allowed in const. @@ -818,6 +807,21 @@ pub fn estimated_capacity(&self) -> usize { } impl<'a> Arguments<'a> { + /// Create a `fmt::Arguments` object for a single static string. + /// + /// Formatting this `fmt::Arguments` will just produce the string as-is. + #[inline] + #[unstable(feature = "fmt_arguments_from_str", issue = "148905")] + pub const fn from_str(s: &'static str) -> Arguments<'a> { + // SAFETY: This is the "static str" representation of fmt::Arguments; see above. + unsafe { + Arguments { + template: mem::transmute(s.as_ptr()), + args: mem::transmute(s.len() << 1 | 1), + } + } + } + /// Gets the formatted string, if it has no arguments to be formatted at runtime. /// /// This can be used to avoid allocations in some cases. diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 6b13b4c4f56c..82cb489ccf40 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -769,13 +769,9 @@ pub const fn select_unpredictable(b: bool, true_val: T, false_val: T) -> T /// // in terms of converting the original inner type (`&i32`) to the new one (`Option<&i32>`), /// // this has all the same caveats. Besides the information provided above, also consult the /// // [`from_raw_parts`] documentation. +/// let (ptr, len, capacity) = v_clone.into_raw_parts(); /// let v_from_raw = unsafe { -// FIXME Update this when vec_into_raw_parts is stabilized -/// // Ensure the original vector is not dropped. -/// let mut v_clone = std::mem::ManuallyDrop::new(v_clone); -/// Vec::from_raw_parts(v_clone.as_mut_ptr() as *mut Option<&i32>, -/// v_clone.len(), -/// v_clone.capacity()) +/// Vec::from_raw_parts(ptr.cast::<*mut Option<&i32>>(), len, capacity) /// }; /// ``` /// diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index df24dd43b82e..6156525b2f59 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -991,7 +991,7 @@ macro_rules! compile_error { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_diagnostic_item = "format_args_macro"] #[allow_internal_unsafe] - #[allow_internal_unstable(fmt_internals)] + #[allow_internal_unstable(fmt_internals, fmt_arguments_from_str)] #[rustc_builtin_macro] #[macro_export] macro_rules! format_args { @@ -1005,7 +1005,7 @@ macro_rules! format_args { /// /// This macro will be removed once `format_args` is allowed in const contexts. #[unstable(feature = "const_format_args", issue = "none")] - #[allow_internal_unstable(fmt_internals, const_fmt_arguments_new)] + #[allow_internal_unstable(fmt_internals, fmt_arguments_from_str)] #[rustc_builtin_macro] #[macro_export] macro_rules! const_format_args { @@ -1020,7 +1020,7 @@ macro_rules! const_format_args { reason = "`format_args_nl` is only for internal \ language use and is subject to change" )] - #[allow_internal_unstable(fmt_internals)] + #[allow_internal_unstable(fmt_internals, fmt_arguments_from_str)] #[rustc_builtin_macro] #[doc(hidden)] #[macro_export] diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index e7101537b298..2cf06b6d6a35 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1770,6 +1770,7 @@ pub fn rem_euclid(self, rhs: f128) -> f128 { /// assert!(abs_difference <= f128::EPSILON); /// /// assert_eq!(f128::powi(f128::NAN, 0), 1.0); + /// assert_eq!(f128::powi(0.0, 0), 1.0); /// # } /// ``` #[inline] diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index aa8342a22ad5..51f803672e5c 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1745,6 +1745,7 @@ pub fn rem_euclid(self, rhs: f16) -> f16 { /// assert!(abs_difference <= f16::EPSILON); /// /// assert_eq!(f16::powi(f16::NAN, 0), 1.0); + /// assert_eq!(f16::powi(0.0, 0), 1.0); /// # } /// ``` #[inline] diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 70e764de9069..6a6853670665 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1720,6 +1720,7 @@ pub const fn strict_abs(self) -> Self { /// /// ``` #[doc = concat!("assert_eq!(8", stringify!($SelfT), ".checked_pow(2), Some(64));")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".checked_pow(0), Some(1));")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.checked_pow(2), None);")] /// ``` @@ -1761,6 +1762,7 @@ pub const fn checked_pow(self, mut exp: u32) -> Option { /// /// ``` #[doc = concat!("assert_eq!(8", stringify!($SelfT), ".strict_pow(2), 64);")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".strict_pow(0), 1);")] /// ``` /// /// The following panics because of overflow: @@ -2033,6 +2035,7 @@ pub const fn saturating_div(self, rhs: Self) -> Self { /// /// ``` #[doc = concat!("assert_eq!((-4", stringify!($SelfT), ").saturating_pow(3), -64);")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".saturating_pow(0), 1);")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MIN.saturating_pow(2), ", stringify!($SelfT), "::MAX);")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MIN.saturating_pow(3), ", stringify!($SelfT), "::MIN);")] /// ``` @@ -2377,6 +2380,7 @@ pub const fn unsigned_abs(self) -> $UnsignedT { #[doc = concat!("assert_eq!(3", stringify!($SelfT), ".wrapping_pow(4), 81);")] /// assert_eq!(3i8.wrapping_pow(5), -13); /// assert_eq!(3i8.wrapping_pow(6), -39); + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".wrapping_pow(0), 1);")] /// ``` #[stable(feature = "no_panic_pow", since = "1.34.0")] #[rustc_const_stable(feature = "const_int_pow", since = "1.50.0")] @@ -2967,6 +2971,7 @@ pub const fn overflowing_abs(self) -> (Self, bool) { /// /// ``` #[doc = concat!("assert_eq!(3", stringify!($SelfT), ".overflowing_pow(4), (81, false));")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".overflowing_pow(0), (1, false));")] /// assert_eq!(3i8.overflowing_pow(5), (-13, true)); /// ``` #[stable(feature = "no_panic_pow", since = "1.34.0")] @@ -3010,6 +3015,7 @@ pub const fn overflowing_pow(self, mut exp: u32) -> (Self, bool) { #[doc = concat!("let x: ", stringify!($SelfT), " = 2; // or any other integer type")] /// /// assert_eq!(x.pow(5), 32); + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".pow(0), 1);")] /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_int_pow", since = "1.50.0")] diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index d38d3a1a5ad4..d638f5551fea 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2055,6 +2055,7 @@ pub const fn shr_exact(self, rhs: u32) -> Option<$SelfT> { /// /// ``` #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".checked_pow(5), Some(32));")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".checked_pow(0), Some(1));")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.checked_pow(2), None);")] /// ``` #[stable(feature = "no_panic_pow", since = "1.34.0")] @@ -2095,6 +2096,7 @@ pub const fn checked_pow(self, mut exp: u32) -> Option { /// /// ``` #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".strict_pow(5), 32);")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".strict_pow(0), 1);")] /// ``` /// /// The following panics because of overflow: @@ -2269,6 +2271,7 @@ pub const fn saturating_div(self, rhs: Self) -> Self { /// /// ``` #[doc = concat!("assert_eq!(4", stringify!($SelfT), ".saturating_pow(3), 64);")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".saturating_pow(0), 1);")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.saturating_pow(2), ", stringify!($SelfT), "::MAX);")] /// ``` #[stable(feature = "no_panic_pow", since = "1.34.0")] @@ -2578,6 +2581,7 @@ pub const fn wrapping_shr(self, rhs: u32) -> Self { /// ``` #[doc = concat!("assert_eq!(3", stringify!($SelfT), ".wrapping_pow(5), 243);")] /// assert_eq!(3u8.wrapping_pow(6), 217); + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".wrapping_pow(0), 1);")] /// ``` #[stable(feature = "no_panic_pow", since = "1.34.0")] #[rustc_const_stable(feature = "const_int_pow", since = "1.50.0")] @@ -3252,6 +3256,7 @@ pub const fn overflowing_shr(self, rhs: u32) -> (Self, bool) { /// /// ``` #[doc = concat!("assert_eq!(3", stringify!($SelfT), ".overflowing_pow(5), (243, false));")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".overflowing_pow(0), (1, false));")] /// assert_eq!(3u8.overflowing_pow(6), (217, true)); /// ``` #[stable(feature = "no_panic_pow", since = "1.34.0")] @@ -3293,6 +3298,7 @@ pub const fn overflowing_pow(self, mut exp: u32) -> (Self, bool) { /// /// ``` #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".pow(5), 32);")] + #[doc = concat!("assert_eq!(0_", stringify!($SelfT), ".pow(0), 1);")] /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_int_pow", since = "1.50.0")] diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs index e1f2ebcf4c28..f68782c804cd 100644 --- a/library/core/src/ops/try_trait.rs +++ b/library/core/src/ops/try_trait.rs @@ -359,11 +359,24 @@ pub fn from_yeet(yeeted: Y) -> T /// and in the other direction, /// ` as Residual>::TryType = Result`. #[unstable(feature = "try_trait_v2_residual", issue = "91285")] -#[rustc_const_unstable(feature = "const_try", issue = "74935")] -pub const trait Residual { +#[rustc_const_unstable(feature = "const_try_residual", issue = "91285")] +pub const trait Residual: Sized { /// The "return" type of this meta-function. #[unstable(feature = "try_trait_v2_residual", issue = "91285")] - type TryType: Try; + type TryType: [const] Try; +} + +/// Used in `try {}` blocks so the type produced in the `?` desugaring +/// depends on the residual type `R` and the output type of the block `O`, +/// but importantly not on the contextual type the way it would be if +/// we called `<_ as FromResidual>::from_residual(r)` directly. +#[unstable(feature = "try_trait_v2_residual", issue = "91285")] +// needs to be `pub` to avoid `private type` errors +#[expect(unreachable_pub)] +#[inline] // FIXME: force would be nice, but fails -- see #148915 +#[lang = "into_try_type"] +pub fn residual_into_try_type, O>(r: R) -> >::TryType { + FromResidual::from_residual(r) } #[unstable(feature = "pub_crate_should_not_need_unstable_attr", issue = "none")] diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs index 1c824e336bed..15ba72bccaa9 100644 --- a/library/core/src/primitive_docs.rs +++ b/library/core/src/primitive_docs.rs @@ -1531,9 +1531,8 @@ mod prim_usize {} /// `&mut T` references can be freely coerced into `&T` references with the same referent type, and /// references with longer lifetimes can be freely coerced into references with shorter ones. /// -/// Reference equality by address, instead of comparing the values pointed to, is accomplished via -/// implicit reference-pointer coercion and raw pointer equality via [`ptr::eq`], while -/// [`PartialEq`] compares values. +/// [`PartialEq`] will compare referenced values. It is possible to compare the reference address +/// using reference-pointer coercion and raw pointer equality via [`ptr::eq`]. /// /// ``` /// use std::ptr; @@ -1648,7 +1647,7 @@ mod prim_usize {} /// For the other direction, things are more complicated: when unsafe code passes arguments /// to safe functions or returns values from safe functions, they generally must *at least* /// not violate these invariants. The full requirements are stronger, as the reference generally -/// must point to data that is safe to use at type `T`. +/// must point to data that is safe to use as type `T`. /// /// It is not decided yet whether unsafe code may violate these invariants temporarily on internal /// data. As a consequence, unsafe code which violates these invariants temporarily on internal data diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index fd067d19fcd9..ea0514f405f1 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -1352,40 +1352,6 @@ pub const fn slice_from_raw_parts_mut(data: *mut T, len: usize) -> *mut [T] { /// assert_eq!(x, [7, 8, 3, 4]); /// assert_eq!(y, [1, 2, 9]); /// ``` -/// -/// # Const evaluation limitations -/// -/// If this function is invoked during const-evaluation, the current implementation has a small (and -/// rarely relevant) limitation: if `count` is at least 2 and the data pointed to by `x` or `y` -/// contains a pointer that crosses the boundary of two `T`-sized chunks of memory, the function may -/// fail to evaluate (similar to a panic during const-evaluation). This behavior may change in the -/// future. -/// -/// The limitation is illustrated by the following example: -/// -/// ``` -/// use std::mem::size_of; -/// use std::ptr; -/// -/// const { unsafe { -/// const PTR_SIZE: usize = size_of::<*const i32>(); -/// let mut data1 = [0u8; PTR_SIZE]; -/// let mut data2 = [0u8; PTR_SIZE]; -/// // Store a pointer in `data1`. -/// data1.as_mut_ptr().cast::<*const i32>().write_unaligned(&42); -/// // Swap the contents of `data1` and `data2` by swapping `PTR_SIZE` many `u8`-sized chunks. -/// // This call will fail, because the pointer in `data1` crosses the boundary -/// // between several of the 1-byte chunks that are being swapped here. -/// //ptr::swap_nonoverlapping(data1.as_mut_ptr(), data2.as_mut_ptr(), PTR_SIZE); -/// // Swap the contents of `data1` and `data2` by swapping a single chunk of size -/// // `[u8; PTR_SIZE]`. That works, as there is no pointer crossing the boundary between -/// // two chunks. -/// ptr::swap_nonoverlapping(&mut data1, &mut data2, 1); -/// // Read the pointer from `data2` and dereference it. -/// let ptr = data2.as_ptr().cast::<*const i32>().read_unaligned(); -/// assert!(*ptr == 42); -/// } } -/// ``` #[inline] #[stable(feature = "swap_nonoverlapping", since = "1.27.0")] #[rustc_const_stable(feature = "const_swap_nonoverlapping", since = "1.88.0")] @@ -1414,9 +1380,7 @@ pub const fn slice_from_raw_parts_mut(data: *mut T, len: usize) -> *mut [T] { const_eval_select!( @capture[T] { x: *mut T, y: *mut T, count: usize }: if const { - // At compile-time we want to always copy this in chunks of `T`, to ensure that if there - // are pointers inside `T` we will copy them in one go rather than trying to copy a part - // of a pointer (which would not work). + // At compile-time we don't need all the special code below. // SAFETY: Same preconditions as this function unsafe { swap_nonoverlapping_const(x, y, count) } } else { diff --git a/library/core/src/range/iter.rs b/library/core/src/range/iter.rs index 24efd4a204a5..9a8824baefe4 100644 --- a/library/core/src/range/iter.rs +++ b/library/core/src/range/iter.rs @@ -3,6 +3,7 @@ }; use crate::num::NonZero; use crate::range::{Range, RangeFrom, RangeInclusive, legacy}; +use crate::{intrinsics, mem}; /// By-value [`Range`] iterator. #[unstable(feature = "new_range_api", issue = "125687")] @@ -168,7 +169,7 @@ pub fn remainder(self) -> Option> { } } -#[unstable(feature = "trusted_random_access", issue = "none")] +#[unstable(feature = "new_range_api", issue = "125687")] impl Iterator for IterRangeInclusive { type Item = A; @@ -293,32 +294,74 @@ impl ExactSizeIterator for IterRangeInclusive<$t> { } /// By-value [`RangeFrom`] iterator. #[unstable(feature = "new_range_api", issue = "125687")] #[derive(Debug, Clone)] -pub struct IterRangeFrom(legacy::RangeFrom); +pub struct IterRangeFrom { + start: A, + /// Whether the first element of the iterator has yielded. + /// Only used when overflow checks are enabled. + first: bool, +} -impl IterRangeFrom { +impl IterRangeFrom { /// Returns the remainder of the range being iterated over. + #[inline] + #[rustc_inherit_overflow_checks] pub fn remainder(self) -> RangeFrom { - RangeFrom { start: self.0.start } + if intrinsics::overflow_checks() { + if !self.first { + return RangeFrom { start: Step::forward(self.start, 1) }; + } + } + + RangeFrom { start: self.start } } } -#[unstable(feature = "trusted_random_access", issue = "none")] +#[unstable(feature = "new_range_api", issue = "125687")] impl Iterator for IterRangeFrom { type Item = A; #[inline] + #[rustc_inherit_overflow_checks] fn next(&mut self) -> Option { - self.0.next() + if intrinsics::overflow_checks() { + if self.first { + self.first = false; + return Some(self.start.clone()); + } + + self.start = Step::forward(self.start.clone(), 1); + return Some(self.start.clone()); + } + + let n = Step::forward(self.start.clone(), 1); + Some(mem::replace(&mut self.start, n)) } #[inline] fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() + (usize::MAX, None) } #[inline] + #[rustc_inherit_overflow_checks] fn nth(&mut self, n: usize) -> Option { - self.0.nth(n) + if intrinsics::overflow_checks() { + if self.first { + self.first = false; + + let plus_n = Step::forward(self.start.clone(), n); + self.start = plus_n.clone(); + return Some(plus_n); + } + + let plus_n = Step::forward(self.start.clone(), n); + self.start = Step::forward(plus_n.clone(), 1); + return Some(self.start.clone()); + } + + let plus_n = Step::forward(self.start.clone(), n); + self.start = Step::forward(plus_n.clone(), 1); + Some(plus_n) } } @@ -334,6 +377,6 @@ impl IntoIterator for RangeFrom { type IntoIter = IterRangeFrom; fn into_iter(self) -> Self::IntoIter { - IterRangeFrom(self.into()) + IterRangeFrom { start: self.start, first: true } } } diff --git a/library/core/src/slice/iter/macros.rs b/library/core/src/slice/iter/macros.rs index 7c1ed3fe8a24..c46b7c797aab 100644 --- a/library/core/src/slice/iter/macros.rs +++ b/library/core/src/slice/iter/macros.rs @@ -350,7 +350,6 @@ fn find_map(&mut self, mut f: F) -> Option // because this simple implementation generates less LLVM IR and is // faster to compile. Also, the `assume` avoids a bounds check. #[inline] - #[rustc_inherit_overflow_checks] fn position

(&mut self, mut predicate: P) -> Option where Self: Sized, P: FnMut(Self::Item) -> bool, diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs index 555a3b01f1fc..93f9454d7137 100644 --- a/library/coretests/tests/ptr.rs +++ b/library/coretests/tests/ptr.rs @@ -945,13 +945,12 @@ struct S { assert!(*s1.0.ptr == 666); assert!(*s2.0.ptr == 1); - // Swap them back, again as an array. - // FIXME(#146291): we should be swapping back at type `u8` but that currently does not work. + // Swap them back, byte-for-byte unsafe { ptr::swap_nonoverlapping( - ptr::from_mut(&mut s1).cast::(), - ptr::from_mut(&mut s2).cast::(), - 1, + ptr::from_mut(&mut s1).cast::(), + ptr::from_mut(&mut s2).cast::(), + size_of::(), ); } diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index fc0fef620e3b..ab21e3b927e2 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -1685,7 +1685,8 @@ pub(super) fn iter(&self) -> Iter<'_, K, V> { /// let iter = map.extract_if(|_k, v| *v % 2 == 0); /// ``` #[stable(feature = "hash_extract_if", since = "1.88.0")] -#[must_use = "iterators are lazy and do nothing unless consumed"] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `retain` to remove and discard elements"] pub struct ExtractIf<'a, K, V, F> { base: base::ExtractIf<'a, K, V, F>, } diff --git a/library/std/src/collections/hash/set.rs b/library/std/src/collections/hash/set.rs index 482d57b47f67..6795da80aacb 100644 --- a/library/std/src/collections/hash/set.rs +++ b/library/std/src/collections/hash/set.rs @@ -1391,6 +1391,8 @@ pub struct Drain<'a, K: 'a> { /// let mut extract_ifed = a.extract_if(|v| v % 2 == 0); /// ``` #[stable(feature = "hash_extract_if", since = "1.88.0")] +#[must_use = "iterators are lazy and do nothing unless consumed; \ + use `retain` to remove and discard elements"] pub struct ExtractIf<'a, K, F> { base: base::ExtractIf<'a, K, F>, } diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 25a4661a0bc9..b7756befa11e 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -330,7 +330,7 @@ stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, util::{Empty, Repeat, Sink, empty, repeat, sink}, }; -use crate::mem::take; +use crate::mem::{MaybeUninit, take}; use crate::ops::{Deref, DerefMut}; use crate::{cmp, fmt, slice, str, sys}; @@ -1242,6 +1242,46 @@ fn take(self, limit: u64) -> Take { Take { inner: self, len: limit, limit } } + + /// Read and return a fixed array of bytes from this source. + /// + /// This function uses an array sized based on a const generic size known at compile time. You + /// can specify the size with turbofish (`reader.read_array::<8>()`), or let type inference + /// determine the number of bytes needed based on how the return value gets used. For instance, + /// this function works well with functions like [`u64::from_le_bytes`] to turn an array of + /// bytes into an integer of the same size. + /// + /// Like `read_exact`, if this function encounters an "end of file" before reading the desired + /// number of bytes, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// + /// ``` + /// #![feature(read_array)] + /// use std::io::Cursor; + /// use std::io::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buf = Cursor::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2]); + /// let x = u64::from_le_bytes(buf.read_array()?); + /// let y = u32::from_be_bytes(buf.read_array()?); + /// let z = u16::from_be_bytes(buf.read_array()?); + /// assert_eq!(x, 0x807060504030201); + /// assert_eq!(y, 0x9080706); + /// assert_eq!(z, 0x504); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "read_array", issue = "148848")] + fn read_array(&mut self) -> Result<[u8; N]> + where + Self: Sized, + { + let mut buf = [MaybeUninit::uninit(); N]; + let mut borrowed_buf = BorrowedBuf::from(buf.as_mut_slice()); + self.read_buf_exact(borrowed_buf.unfilled())?; + // Guard against incorrect `read_buf_exact` implementations. + assert_eq!(borrowed_buf.len(), N); + Ok(unsafe { MaybeUninit::array_assume_init(buf) }) + } } /// Reads all bytes from a [reader][Read] into a new [`String`]. diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 7b6cfbfe0f25..79deb3221ce5 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -348,6 +348,7 @@ #![feature(int_from_ascii)] #![feature(ip)] #![feature(lazy_get)] +#![feature(maybe_uninit_array_assume_init)] #![feature(maybe_uninit_slice)] #![feature(maybe_uninit_write_slice)] #![feature(panic_can_unwind)] @@ -381,7 +382,6 @@ #![feature(try_reserve_kind)] #![feature(try_with_capacity)] #![feature(unique_rc_arc)] -#![feature(vec_into_raw_parts)] #![feature(wtf8_internals)] // tidy-alphabetical-end // diff --git a/library/std/src/num/f128.rs b/library/std/src/num/f128.rs index 40061d089284..3b787713afa2 100644 --- a/library/std/src/num/f128.rs +++ b/library/std/src/num/f128.rs @@ -37,6 +37,7 @@ impl f128 { /// /// assert_eq!(f128::powf(1.0, f128::NAN), 1.0); /// assert_eq!(f128::powf(f128::NAN, 0.0), 1.0); + /// assert_eq!(f128::powf(0.0, 0.0), 1.0); /// # } /// ``` #[inline] diff --git a/library/std/src/num/f16.rs b/library/std/src/num/f16.rs index 0d43b60a62fe..4af21c95c9ba 100644 --- a/library/std/src/num/f16.rs +++ b/library/std/src/num/f16.rs @@ -37,6 +37,7 @@ impl f16 { /// /// assert_eq!(f16::powf(1.0, f16::NAN), 1.0); /// assert_eq!(f16::powf(f16::NAN, 0.0), 1.0); + /// assert_eq!(f16::powf(0.0, 0.0), 1.0); /// # } /// ``` #[inline] diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs index c9e192201aff..09ced388a339 100644 --- a/library/std/src/num/f32.rs +++ b/library/std/src/num/f32.rs @@ -308,6 +308,7 @@ pub fn rem_euclid(self, rhs: f32) -> f32 { /// assert!(abs_difference <= 1e-5); /// /// assert_eq!(f32::powi(f32::NAN, 0), 1.0); + /// assert_eq!(f32::powi(0.0, 0), 1.0); /// ``` #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] @@ -333,6 +334,7 @@ pub fn powi(self, n: i32) -> f32 { /// /// assert_eq!(f32::powf(1.0, f32::NAN), 1.0); /// assert_eq!(f32::powf(f32::NAN, 0.0), 1.0); + /// assert_eq!(f32::powf(0.0, 0.0), 1.0); /// ``` #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs index 11874f9280f0..79adf076e4b1 100644 --- a/library/std/src/num/f64.rs +++ b/library/std/src/num/f64.rs @@ -308,6 +308,7 @@ pub fn rem_euclid(self, rhs: f64) -> f64 { /// assert!(abs_difference <= 1e-14); /// /// assert_eq!(f64::powi(f64::NAN, 0), 1.0); + /// assert_eq!(f64::powi(0.0, 0), 1.0); /// ``` #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] @@ -333,6 +334,7 @@ pub fn powi(self, n: i32) -> f64 { /// /// assert_eq!(f64::powf(1.0, f64::NAN), 1.0); /// assert_eq!(f64::powf(f64::NAN, 0.0), 1.0); + /// assert_eq!(f64::powf(0.0, 0.0), 1.0); /// ``` #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 5b7b5a8ea803..ee0c460f7dfa 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -80,6 +80,9 @@ pub trait CommandExt: Sealed { /// or acquiring a mutex are not guaranteed to work (due to /// other threads perhaps still running when the `fork` was run). /// + /// Note that the list of allocating functions includes [`Error::new`] and + /// [`Error::other`]. To signal a non-trivial error, prefer [`panic!`]. + /// /// For further details refer to the [POSIX fork() specification] /// and the equivalent documentation for any targeted /// platform, especially the requirements around *async-signal-safety*. @@ -102,6 +105,8 @@ pub trait CommandExt: Sealed { /// [POSIX fork() specification]: /// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html /// [`std::env`]: mod@crate::env + /// [`Error::new`]: crate::io::Error::new + /// [`Error::other`]: crate::io::Error::other #[stable(feature = "process_pre_exec", since = "1.34.0")] unsafe fn pre_exec(&mut self, f: F) -> &mut process::Command where diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index e4e7274ae8cb..fc5f159ec188 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -18,6 +18,9 @@ pub struct FileAttr { attr: u64, size: u64, + accessed: SystemTime, + modified: SystemTime, + created: SystemTime, } pub struct ReadDir(!); @@ -33,7 +36,10 @@ pub struct OpenOptions { } #[derive(Copy, Clone, Debug, Default)] -pub struct FileTimes {} +pub struct FileTimes { + accessed: Option, + modified: Option, +} #[derive(Clone, PartialEq, Eq, Debug)] // Bool indicates if file is readonly @@ -60,15 +66,15 @@ pub fn file_type(&self) -> FileType { } pub fn modified(&self) -> io::Result { - unsupported() + Ok(self.modified) } pub fn accessed(&self) -> io::Result { - unsupported() + Ok(self.accessed) } pub fn created(&self) -> io::Result { - unsupported() + Ok(self.created) } } @@ -92,8 +98,13 @@ const fn to_attr(&self) -> u64 { } impl FileTimes { - pub fn set_accessed(&mut self, _t: SystemTime) {} - pub fn set_modified(&mut self, _t: SystemTime) {} + pub fn set_accessed(&mut self, t: SystemTime) { + self.accessed = Some(t); + } + + pub fn set_modified(&mut self, t: SystemTime) { + self.modified = Some(t); + } } impl FileType { @@ -394,6 +405,7 @@ mod uefi_fs { use crate::path::Path; use crate::ptr::NonNull; use crate::sys::helpers; + use crate::sys::time::{self, SystemTime}; pub(crate) struct File(NonNull); @@ -541,4 +553,23 @@ pub(crate) fn mkdir(path: &Path) -> io::Result<()> { Ok(()) } + + /// EDK2 FAT driver uses EFI_UNSPECIFIED_TIMEZONE to represent localtime. So for proper + /// conversion to SystemTime, we use the current time to get the timezone in such cases. + #[expect(dead_code)] + fn uefi_to_systemtime(mut time: r_efi::efi::Time) -> SystemTime { + time.timezone = if time.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { + time::system_time_internal::now().unwrap().timezone + } else { + time.timezone + }; + SystemTime::from_uefi(time) + } + + /// Convert to UEFI Time with the current timezone. + #[expect(dead_code)] + fn systemtime_to_uefi(time: SystemTime) -> r_efi::efi::Time { + let now = time::system_time_internal::now().unwrap(); + time.to_uefi_loose(now.timezone, now.daylight) + } } diff --git a/library/std/src/sys/pal/uefi/tests.rs b/library/std/src/sys/pal/uefi/tests.rs index 56ca999cc7e9..df3344e2df34 100644 --- a/library/std/src/sys/pal/uefi/tests.rs +++ b/library/std/src/sys/pal/uefi/tests.rs @@ -8,6 +8,20 @@ const SECS_IN_MINUTE: u64 = 60; +const MAX_UEFI_TIME: Duration = from_uefi(r_efi::efi::Time { + year: 9999, + month: 12, + day: 31, + hour: 23, + minute: 59, + second: 59, + nanosecond: 999_999_999, + timezone: 1440, + daylight: 0, + pad1: 0, + pad2: 0, +}); + #[test] fn align() { // UEFI ABI specifies that allocation alignment minimum is always 8. So this can be @@ -28,6 +42,19 @@ fn align() { } } +// UEFI Time cannot implement Eq due to uninitilaized pad1 and pad2 +fn uefi_time_cmp(t1: r_efi::efi::Time, t2: r_efi::efi::Time) -> bool { + t1.year == t2.year + && t1.month == t2.month + && t1.day == t2.day + && t1.hour == t2.hour + && t1.minute == t2.minute + && t1.second == t2.second + && t1.nanosecond == t2.nanosecond + && t1.timezone == t2.timezone + && t1.daylight == t2.daylight +} + #[test] fn systemtime_start() { let t = r_efi::efi::Time { @@ -37,14 +64,15 @@ fn systemtime_start() { hour: 0, minute: 0, second: 0, + pad1: 0, nanosecond: 0, timezone: -1440, daylight: 0, pad2: 0, }; assert_eq!(from_uefi(&t), Duration::new(0, 0)); - assert_eq!(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap()); - assert!(to_uefi(&from_uefi(&t), 0, 0).is_none()); + assert!(uefi_time_cmp(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap())); + assert!(to_uefi(&from_uefi(&t), 0, 0).is_err()); } #[test] @@ -63,8 +91,8 @@ fn systemtime_utc_start() { pad2: 0, }; assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0)); - assert_eq!(t, to_uefi(&from_uefi(&t), 0, 0).unwrap()); - assert!(to_uefi(&from_uefi(&t), -1440, 0).is_some()); + assert!(uefi_time_cmp(t, to_uefi(&from_uefi(&t), 0, 0).unwrap())); + assert!(to_uefi(&from_uefi(&t), -1440, 0).is_ok()); } #[test] @@ -82,8 +110,49 @@ fn systemtime_end() { daylight: 0, pad2: 0, }; - assert!(to_uefi(&from_uefi(&t), 1440, 0).is_some()); - assert!(to_uefi(&from_uefi(&t), 1439, 0).is_none()); + assert!(to_uefi(&from_uefi(&t), 1440, 0).is_ok()); + assert!(to_uefi(&from_uefi(&t), 1439, 0).is_err()); +} + +#[test] +fn min_time() { + let inp = Duration::from_secs(1440 * SECS_IN_MINUTE); + let new_tz = to_uefi(&inp, 1440, 0).err().unwrap(); + assert_eq!(new_tz, 0); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); + + let inp = Duration::from_secs(1450 * SECS_IN_MINUTE); + let new_tz = to_uefi(&inp, 1440, 0).err().unwrap(); + assert_eq!(new_tz, 10); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); + + let inp = Duration::from_secs(1450 * SECS_IN_MINUTE + 10); + let new_tz = to_uefi(&inp, 1440, 0).err().unwrap(); + assert_eq!(new_tz, 10); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); + + let inp = Duration::from_secs(1430 * SECS_IN_MINUTE); + let new_tz = to_uefi(&inp, 1440, 0).err().unwrap(); + assert_eq!(new_tz, -10); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); +} + +#[test] +fn max_time() { + let inp = MAX_UEFI_TIME.0; + let new_tz = to_uefi(&inp, -1440, 0).err().unwrap(); + assert_eq!(new_tz, 1440); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); + + let inp = MAX_UEFI_TIME.0 - Duration::from_secs(1440 * SECS_IN_MINUTE); + let new_tz = to_uefi(&inp, -1440, 0).err().unwrap(); + assert_eq!(new_tz, 0); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); + + let inp = MAX_UEFI_TIME.0 - Duration::from_secs(1440 * SECS_IN_MINUTE + 10); + let new_tz = to_uefi(&inp, -1440, 0).err().unwrap(); + assert_eq!(new_tz, 0); + assert!(to_uefi(&inp, new_tz, 0).is_ok()); } // UEFI IoSlice and IoSliceMut Tests diff --git a/library/std/src/sys/pal/uefi/time.rs b/library/std/src/sys/pal/uefi/time.rs index 861b98da18da..f9f90a454976 100644 --- a/library/std/src/sys/pal/uefi/time.rs +++ b/library/std/src/sys/pal/uefi/time.rs @@ -1,5 +1,7 @@ use crate::time::Duration; +const SECS_IN_MINUTE: u64 = 60; + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Instant(Duration); @@ -70,13 +72,32 @@ pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Self { Self(system_time_internal::from_uefi(&t)) } - #[expect(dead_code)] - pub(crate) const fn to_uefi(self, timezone: i16, daylight: u8) -> Option { - system_time_internal::to_uefi(&self.0, timezone, daylight) + pub(crate) const fn to_uefi( + self, + timezone: i16, + daylight: u8, + ) -> Result { + // system_time_internal::to_uefi requires a valid timezone. In case of unspecified timezone, + // we just pass 0 since it is assumed that no timezone related adjustments are required. + if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { + system_time_internal::to_uefi(&self.0, 0, daylight) + } else { + system_time_internal::to_uefi(&self.0, timezone, daylight) + } + } + + /// Create UEFI Time with the closest timezone (minute offset) that still allows the time to be + /// represented. + pub(crate) fn to_uefi_loose(self, timezone: i16, daylight: u8) -> r_efi::efi::Time { + match self.to_uefi(timezone, daylight) { + Ok(x) => x, + Err(tz) => self.to_uefi(tz, daylight).unwrap(), + } } pub fn now() -> SystemTime { system_time_internal::now() + .map(Self::from_uefi) .unwrap_or_else(|| panic!("time not implemented on this platform")) } @@ -104,12 +125,11 @@ pub(crate) mod system_time_internal { use crate::mem::MaybeUninit; use crate::ptr::NonNull; - const SECS_IN_MINUTE: u64 = 60; const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60; const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24; - const TIMEZONE_DELTA: u64 = 1440 * SECS_IN_MINUTE; + const SYSTEMTIME_TIMEZONE: i64 = -1440 * SECS_IN_MINUTE as i64; - pub fn now() -> Option { + pub(crate) fn now() -> Option{text}"#, + r#"{text}"#, path = join_path_syms(rust_path), text = EscapeBodyText(text.as_str()), ) diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 4c06d0da4701..e42997d5b4a1 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -203,52 +203,54 @@ fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String { // `record_extern_fqn` correctly points to external items. render_redirect_pages = true; } - let mut title = String::new(); - if !is_module { - title.push_str(it.name.unwrap().as_str()); - } - let short_title; - let short_title = if is_module { - let module_name = self.current.last().unwrap(); - short_title = if it.is_crate() { - format!("Crate {module_name}") - } else { - format!("Module {module_name}") - }; - &short_title[..] - } else { - it.name.as_ref().unwrap().as_str() - }; - if !it.is_fake_item() { - if !is_module { - title.push_str(" in "); - } - // No need to include the namespace for primitive types and keywords - title.push_str(&join_path_syms(&self.current)); - }; - title.push_str(" - Rust"); - let tyname = it.type_(); - let desc = plain_text_summary(&it.doc_value(), &it.link_names(self.cache())); - let desc = if !desc.is_empty() { - desc - } else if it.is_crate() { - format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) - } else { - format!( - "API documentation for the Rust `{name}` {tyname} in crate `{krate}`.", - name = it.name.as_ref().unwrap(), - krate = self.shared.layout.krate, - ) - }; - let name; - let tyname_s = if it.is_crate() { - name = format!("{tyname} crate"); - name.as_str() - } else { - tyname.as_str() - }; if !render_redirect_pages { + let mut title = String::new(); + if !is_module { + title.push_str(it.name.unwrap().as_str()); + } + let short_title; + let short_title = if is_module { + let module_name = self.current.last().unwrap(); + short_title = if it.is_crate() { + format!("Crate {module_name}") + } else { + format!("Module {module_name}") + }; + &short_title[..] + } else { + it.name.as_ref().unwrap().as_str() + }; + if !it.is_fake_item() { + if !is_module { + title.push_str(" in "); + } + // No need to include the namespace for primitive types and keywords + title.push_str(&join_path_syms(&self.current)); + }; + title.push_str(" - Rust"); + let tyname = it.type_(); + let desc = plain_text_summary(&it.doc_value(), &it.link_names(self.cache())); + let desc = if !desc.is_empty() { + desc + } else if it.is_crate() { + format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) + } else { + format!( + "API documentation for the Rust `{name}` {tyname} in crate `{krate}`.", + name = it.name.as_ref().unwrap(), + krate = self.shared.layout.krate, + ) + }; + + let name; + let tyname_s = if it.is_crate() { + name = format!("{tyname} crate"); + name.as_str() + } else { + tyname.as_str() + }; + let content = print_item(self, it); let page = layout::Page { css_class: tyname_s, diff --git a/src/librustdoc/html/render/search_index/encode.rs b/src/librustdoc/html/render/search_index/encode.rs index d15e13a2d374..a05c14374d1d 100644 --- a/src/librustdoc/html/render/search_index/encode.rs +++ b/src/librustdoc/html/render/search_index/encode.rs @@ -78,16 +78,9 @@ pub fn read_postings_from_string(postings: &mut Vec>, mut buf: &[u8]) { while let Some(&c) = buf.get(0) { if c < 0x3a { buf = &buf[1..]; - let mut slot = Vec::new(); - for _ in 0..c { - slot.push( - (buf[0] as u32) - | ((buf[1] as u32) << 8) - | ((buf[2] as u32) << 16) - | ((buf[3] as u32) << 24), - ); - buf = &buf[4..]; - } + let buf = buf.split_off(..usize::from(c) * size_of::()).unwrap(); + let (chunks, _) = buf.as_chunks(); + let slot = chunks.iter().copied().map(u32::from_le_bytes).collect(); postings.push(slot); } else { let (bitmap, consumed_bytes_len) = diff --git a/src/tools/build-manifest/Cargo.toml b/src/tools/build-manifest/Cargo.toml index efa99f181b3a..c0271bbc89b5 100644 --- a/src/tools/build-manifest/Cargo.toml +++ b/src/tools/build-manifest/Cargo.toml @@ -14,3 +14,7 @@ tar = "0.4.29" sha2 = "0.10.1" rayon = "1.5.1" hex = "0.4.2" + +[build-dependencies] +serde = "1" +serde_json = "1" diff --git a/src/tools/build-manifest/README.md b/src/tools/build-manifest/README.md index 2ea1bffb35f4..bc1992ef80cc 100644 --- a/src/tools/build-manifest/README.md +++ b/src/tools/build-manifest/README.md @@ -2,7 +2,13 @@ This tool generates the manifests uploaded to static.rust-lang.org and used by rustup. You can see a full list of all manifests at . -This listing is updated by every 7 days. + +We auto-generate the host targets (those with full compiler toolchains) and +target targets (a superset of hosts, some of which only support std) through +`build.rs`, which internally uses a stage 1 rustc to produce the target list +and uses the `TargetMetadata` to determine whether host tools are expected and +whether artifacts are expected. This list is not currently verified against the +actually produced artifacts by CI, though that may change in the future. This gets called by `promote-release` . `promote-release` downloads a pre-built binary of `build-manifest` which is generated in the dist-x86_64-linux builder and uploaded to s3. diff --git a/src/tools/build-manifest/build.rs b/src/tools/build-manifest/build.rs new file mode 100644 index 000000000000..c804921408a9 --- /dev/null +++ b/src/tools/build-manifest/build.rs @@ -0,0 +1,76 @@ +use std::fmt::Write; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +#[derive(Default, Debug)] +pub(crate) struct RustcTargets { + /// Targets with host tool artifacts. + pub(crate) hosts: Vec, + + /// All targets we distribute some artifacts for (superset of `hosts`). + pub(crate) targets: Vec, +} + +fn collect_rustc_targets() -> RustcTargets { + let rustc_path = std::env::var("RUSTC").expect("RUSTC set"); + let output = Command::new(&rustc_path) + .arg("--print=all-target-specs-json") + .env("RUSTC_BOOTSTRAP", "1") + .arg("-Zunstable-options") + .stderr(Stdio::inherit()) + .output() + .unwrap(); + assert!(output.status.success()); + let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + + let mut rustc_targets = RustcTargets::default(); + for (target, json) in json.as_object().unwrap().iter() { + let Some(tier) = json["metadata"]["tier"].as_u64() else { + eprintln!("skipping {target}: no tier in metadata"); + continue; + }; + let host_tools: Option = + serde_json::from_value(json["metadata"]["host_tools"].clone()).unwrap(); + + if !(tier == 1 || tier == 2) { + eprintln!("ignoring {target}: tier {tier} insufficient for target to be in manifest"); + continue; + } + + if host_tools == Some(true) { + rustc_targets.hosts.push(target.to_owned()); + rustc_targets.targets.push(target.to_owned()); + } else { + rustc_targets.targets.push(target.to_owned()); + } + } + + rustc_targets +} + +fn main() { + let targets = collect_rustc_targets(); + + // Verify we ended up with a reasonable target list. + assert!(targets.hosts.len() >= 10); + assert!(targets.targets.len() >= 30); + assert!(targets.hosts.iter().any(|e| e == "x86_64-unknown-linux-gnu")); + assert!(targets.targets.iter().any(|e| e == "x86_64-unknown-linux-gnu")); + + let mut output = String::new(); + + writeln!(output, "static HOSTS: &[&str] = &[").unwrap(); + for host in targets.hosts { + writeln!(output, " {:?},", host).unwrap(); + } + writeln!(output, "];").unwrap(); + + writeln!(output, "static TARGETS: &[&str] = &[").unwrap(); + for target in targets.targets { + writeln!(output, " {:?},", target).unwrap(); + } + writeln!(output, "];").unwrap(); + + std::fs::write(PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("targets.rs"), output) + .unwrap(); +} diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 9bae8b241a94..5d9d63093860 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -12,193 +12,7 @@ use crate::manifest::{Component, Manifest, Package, Rename, Target}; use crate::versions::{PkgType, Versions}; -static HOSTS: &[&str] = &[ - "aarch64-apple-darwin", - "aarch64-pc-windows-gnullvm", - "aarch64-pc-windows-msvc", - "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl", - "arm-unknown-linux-gnueabi", - "arm-unknown-linux-gnueabihf", - "armv7-unknown-linux-gnueabihf", - "i686-apple-darwin", - "i686-pc-windows-gnu", - "i686-pc-windows-msvc", - "i686-unknown-linux-gnu", - "loongarch64-unknown-linux-gnu", - "loongarch64-unknown-linux-musl", - "mips-unknown-linux-gnu", - "mips64-unknown-linux-gnuabi64", - "mips64el-unknown-linux-gnuabi64", - "mipsel-unknown-linux-gnu", - "mipsisa32r6-unknown-linux-gnu", - "mipsisa32r6el-unknown-linux-gnu", - "mipsisa64r6-unknown-linux-gnuabi64", - "mipsisa64r6el-unknown-linux-gnuabi64", - "powerpc-unknown-linux-gnu", - "powerpc64-unknown-linux-gnu", - "powerpc64le-unknown-linux-gnu", - "powerpc64le-unknown-linux-musl", - "riscv64gc-unknown-linux-gnu", - "s390x-unknown-linux-gnu", - "sparcv9-sun-solaris", - "x86_64-apple-darwin", - "x86_64-pc-solaris", - "x86_64-pc-windows-gnu", - "x86_64-pc-windows-gnullvm", - "x86_64-pc-windows-msvc", - "x86_64-unknown-freebsd", - "x86_64-unknown-illumos", - "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", - "x86_64-unknown-netbsd", -]; - -static TARGETS: &[&str] = &[ - "aarch64-apple-darwin", - "aarch64-apple-ios", - "aarch64-apple-ios-macabi", - "aarch64-apple-ios-sim", - "aarch64-unknown-fuchsia", - "aarch64-linux-android", - "aarch64-pc-windows-gnullvm", - "aarch64-pc-windows-msvc", - "aarch64-unknown-hermit", - "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl", - "aarch64-unknown-linux-ohos", - "aarch64-unknown-none", - "aarch64-unknown-none-softfloat", - "aarch64-unknown-redox", - "aarch64-unknown-uefi", - "aarch64-unknown-managarm-mlibc", - "amdgcn-amd-amdhsa", - "arm64e-apple-darwin", - "arm64e-apple-ios", - "arm64e-apple-tvos", - "arm-linux-androideabi", - "arm-unknown-linux-gnueabi", - "arm-unknown-linux-gnueabihf", - "arm-unknown-linux-musleabi", - "arm-unknown-linux-musleabihf", - "arm64ec-pc-windows-msvc", - "armv5te-unknown-linux-gnueabi", - "armv5te-unknown-linux-musleabi", - "armv7-linux-androideabi", - "thumbv7neon-linux-androideabi", - "armv7-unknown-linux-gnueabi", - "armv7-unknown-linux-gnueabihf", - "armv7a-none-eabi", - "armv7a-none-eabihf", - "thumbv7neon-unknown-linux-gnueabihf", - "armv7-unknown-linux-musleabi", - "armv7-unknown-linux-musleabihf", - "armv7-unknown-linux-ohos", - "armebv7r-none-eabi", - "armebv7r-none-eabihf", - "armv7r-none-eabi", - "armv7r-none-eabihf", - "armv8r-none-eabihf", - "armv7s-apple-ios", - "bpfeb-unknown-none", - "bpfel-unknown-none", - "i386-apple-ios", - "i586-unknown-linux-gnu", - "i586-unknown-linux-musl", - "i586-unknown-redox", - "i686-apple-darwin", - "i686-linux-android", - "i686-pc-windows-gnu", - "i686-pc-windows-gnullvm", - "i686-pc-windows-msvc", - "i686-unknown-freebsd", - "i686-unknown-linux-gnu", - "i686-unknown-linux-musl", - "i686-unknown-uefi", - "loongarch64-unknown-linux-gnu", - "loongarch64-unknown-linux-musl", - "loongarch32-unknown-none", - "loongarch32-unknown-none-softfloat", - "loongarch64-unknown-none", - "loongarch64-unknown-none-softfloat", - "m68k-unknown-linux-gnu", - "m68k-unknown-none-elf", - "csky-unknown-linux-gnuabiv2", - "csky-unknown-linux-gnuabiv2hf", - "mips-unknown-linux-gnu", - "mips-unknown-linux-musl", - "mips64-unknown-linux-gnuabi64", - "mips64-unknown-linux-muslabi64", - "mips64el-unknown-linux-gnuabi64", - "mips64el-unknown-linux-muslabi64", - "mipsisa32r6-unknown-linux-gnu", - "mipsisa32r6el-unknown-linux-gnu", - "mipsisa64r6-unknown-linux-gnuabi64", - "mipsisa64r6el-unknown-linux-gnuabi64", - "mipsel-unknown-linux-gnu", - "mipsel-unknown-linux-musl", - "mips-mti-none-elf", - "mipsel-mti-none-elf", - "nvptx64-nvidia-cuda", - "powerpc-unknown-linux-gnu", - "powerpc64-unknown-linux-gnu", - "powerpc64le-unknown-linux-gnu", - "powerpc64le-unknown-linux-musl", - "riscv32i-unknown-none-elf", - "riscv32im-risc0-zkvm-elf", - "riscv32im-unknown-none-elf", - "riscv32ima-unknown-none-elf", - "riscv32imc-unknown-none-elf", - "riscv32imac-unknown-none-elf", - "riscv32imafc-unknown-none-elf", - "riscv32gc-unknown-linux-gnu", - "riscv64imac-unknown-none-elf", - "riscv64gc-unknown-hermit", - "riscv64gc-unknown-none-elf", - "riscv64gc-unknown-linux-gnu", - "riscv64gc-unknown-linux-musl", - "riscv64gc-unknown-managarm-mlibc", - "s390x-unknown-linux-gnu", - "sparc64-unknown-linux-gnu", - "sparcv9-sun-solaris", - "sparc-unknown-none-elf", - "thumbv6m-none-eabi", - "thumbv7em-none-eabi", - "thumbv7em-none-eabihf", - "thumbv7m-none-eabi", - "thumbv8m.base-none-eabi", - "thumbv8m.main-none-eabi", - "thumbv8m.main-none-eabihf", - "wasm32-unknown-emscripten", - "wasm32-unknown-unknown", - "wasm32-wasip1", - "wasm32-wasip1-threads", - "wasm32-wasip2", - "wasm32v1-none", - "x86_64-apple-darwin", - "x86_64-apple-ios", - "x86_64-apple-ios-macabi", - "x86_64-fortanix-unknown-sgx", - "x86_64-unknown-fuchsia", - "x86_64-linux-android", - "x86_64-pc-windows-gnu", - "x86_64-pc-windows-gnullvm", - "x86_64-pc-windows-msvc", - "x86_64-pc-solaris", - "x86_64-unikraft-linux-musl", - "x86_64-unknown-freebsd", - "x86_64-unknown-illumos", - "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-gnux32", - "x86_64-unknown-linux-musl", - "x86_64-unknown-linux-ohos", - "x86_64-unknown-netbsd", - "x86_64-unknown-none", - "x86_64-unknown-redox", - "x86_64-unknown-hermit", - "x86_64-unknown-uefi", - "x86_64-unknown-managarm-mlibc", -]; +include!(concat!(env!("OUT_DIR"), "/targets.rs")); /// This allows the manifest to contain rust-docs for hosts that don't build /// docs. diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.yml b/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.yml index b6f70a7f1830..91aaf1f3644e 100644 --- a/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE/bug_report.yml @@ -20,15 +20,15 @@ body: label: Reproducer description: Please provide the code and steps to reproduce the bug value: | - I tried this code: + Code: ```rust ``` - I expected to see this happen: + Current output: - Instead, this happened: + Desired output: - type: textarea id: version attributes: diff --git a/src/tools/clippy/.github/workflows/clippy_mq.yml b/src/tools/clippy/.github/workflows/clippy_mq.yml index 9d099137449e..ce15a861bb07 100644 --- a/src/tools/clippy/.github/workflows/clippy_mq.yml +++ b/src/tools/clippy/.github/workflows/clippy_mq.yml @@ -25,8 +25,6 @@ jobs: host: i686-unknown-linux-gnu - os: windows-latest host: x86_64-pc-windows-msvc - - os: macos-13 - host: x86_64-apple-darwin - os: macos-latest host: aarch64-apple-darwin diff --git a/src/tools/clippy/clippy_config/src/conf.rs b/src/tools/clippy/clippy_config/src/conf.rs index 2a042e6c3d85..8cdd99ac44a8 100644 --- a/src/tools/clippy/clippy_config/src/conf.rs +++ b/src/tools/clippy/clippy_config/src/conf.rs @@ -108,7 +108,7 @@ struct ConfError { impl ConfError { fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self { - let span = error.span().unwrap_or(0..file.source_len.0 as usize); + let span = error.span().unwrap_or(0..file.normalized_source_len.0 as usize); Self::spanned(file, error.message(), None, span) } diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs index a180db6ad062..0b6d702d7721 100644 --- a/src/tools/clippy/clippy_dev/src/new_lint.rs +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -157,18 +157,19 @@ fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> { let path = "clippy_lints/src/lib.rs"; let mut lib_rs = fs::read_to_string(path).context("reading")?; - let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment"); - let ctor_arg = if lint.pass == Pass::Late { "_" } else { "" }; - let lint_pass = lint.pass; + let (comment, ctor_arg) = if lint.pass == Pass::Late { + ("// add late passes here", "_") + } else { + ("// add early passes here", "") + }; + let comment_start = lib_rs.find(comment).expect("Couldn't find comment"); let module_name = lint.name; let camel_name = to_camel_case(lint.name); let new_lint = if enable_msrv { - format!( - "store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(conf)));\n ", - ) + format!("Box::new(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(conf))),\n ",) } else { - format!("store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n ",) + format!("Box::new(|{ctor_arg}| Box::new({module_name}::{camel_name})),\n ",) }; lib_rs.insert_str(comment_start, &new_lint); diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs index f3985603c4d2..902ba70577b9 100644 --- a/src/tools/clippy/clippy_lints/src/booleans.rs +++ b/src/tools/clippy/clippy_lints/src/booleans.rs @@ -3,7 +3,7 @@ use clippy_utils::higher::has_let_expr; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::{eq_expr_value, sym}; @@ -415,19 +415,20 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio BinOpKind::Ge => Some(" < "), _ => None, } - .and_then(|op| { - let lhs_snippet = lhs.span.get_source_text(cx)?; - let rhs_snippet = rhs.span.get_source_text(cx)?; + .map(|op| { + let mut app = Applicability::MachineApplicable; + let (lhs_snippet, _) = snippet_with_context(cx, lhs.span, SyntaxContext::root(), "", &mut app); + let (rhs_snippet, _) = snippet_with_context(cx, rhs.span, SyntaxContext::root(), "", &mut app); if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) && let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) { // e.g. `(a as u64) < b`. Without the parens the `<` is // interpreted as a start of generic arguments for `u64` - return Some(format!("({lhs_snippet}){op}{rhs_snippet}")); + return format!("({lhs_snippet}){op}{rhs_snippet}"); } - Some(format!("{lhs_snippet}{op}{rhs_snippet}")) + format!("{lhs_snippet}{op}{rhs_snippet}") }) }, ExprKind::MethodCall(path, receiver, args, _) => { diff --git a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs index dfc8411baa00..7f3ef58c93d1 100644 --- a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs +++ b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs @@ -2,14 +2,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, peel_blocks, sym, + as_some_expr, contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, + is_none_expr, peel_blocks, sym, }; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -70,11 +69,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { }) = higher::If::hir(expr) && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr - && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && let Some(then_arg) = as_some_expr(cx, then_expr) && !expr.span.from_expansion() && !then_expr.span.from_expansion() - && then_call.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && peel_blocks(els).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + && is_none_expr(cx, peel_blocks(els)) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) && self.msrv.meets(cx, msrvs::BOOL_THEN) diff --git a/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs b/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs index 716334656926..c3bc9048c23a 100644 --- a/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs +++ b/src/tools/clippy/clippy_lints/src/incompatible_msrv.rs @@ -1,14 +1,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; -use clippy_utils::{is_in_const_context, is_in_test}; +use clippy_utils::{is_in_const_context, is_in_test, sym}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::{CrateNum, DefId}; -use rustc_span::{ExpnKind, Span, sym}; +use rustc_span::{ExpnKind, Span}; declare_clippy_lint! { /// ### What it does @@ -77,11 +77,36 @@ enum Availability { Since(RustcVersion), } +/// All known std crates containing a stability attribute. +struct StdCrates([Option; 6]); +impl StdCrates { + fn new(tcx: TyCtxt<'_>) -> Self { + let mut res = Self([None; _]); + for &krate in tcx.crates(()) { + // FIXME(@Jarcho): We should have an internal lint to detect when this list is out of date. + match tcx.crate_name(krate) { + sym::alloc => res.0[0] = Some(krate), + sym::core => res.0[1] = Some(krate), + sym::core_arch => res.0[2] = Some(krate), + sym::proc_macro => res.0[3] = Some(krate), + sym::std => res.0[4] = Some(krate), + sym::std_detect => res.0[5] = Some(krate), + _ => {}, + } + } + res + } + + fn contains(&self, krate: CrateNum) -> bool { + self.0.contains(&Some(krate)) + } +} + pub struct IncompatibleMsrv { msrv: Msrv, availability_cache: FxHashMap<(DefId, bool), Availability>, check_in_tests: bool, - core_crate: Option, + std_crates: StdCrates, // The most recently called path. Used to skip checking the path after it's // been checked when visiting the call expression. @@ -96,11 +121,7 @@ pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { msrv: conf.msrv, availability_cache: FxHashMap::default(), check_in_tests: conf.check_incompatible_msrv_in_tests, - core_crate: tcx - .crates(()) - .iter() - .find(|krate| tcx.crate_name(**krate) == sym::core) - .copied(), + std_crates: StdCrates::new(tcx), called_path: None, } } @@ -152,21 +173,24 @@ fn emit_lint_if_under_msrv( node: HirId, span: Span, ) { - if def_id.is_local() { - // We don't check local items since their MSRV is supposed to always be valid. + if !self.std_crates.contains(def_id.krate) { + // No stability attributes to lookup for these items. return; } - let expn_data = span.ctxt().outer_expn_data(); - if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = expn_data.kind { - // Desugared expressions get to cheat and stability is ignored. - // Intentionally not using `.from_expansion()`, since we do still care about macro expansions - return; - } - // Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the - // macros may have existed prior to the checked MSRV, but their expansion with a recent compiler - // might use recent functions or methods. Compiling with an older compiler would not use those. - if Some(def_id.krate) == self.core_crate && expn_data.macro_def_id.map(|did| did.krate) == self.core_crate { - return; + // Use `from_expansion` to fast-path the common case. + if span.from_expansion() { + let expn = span.ctxt().outer_expn_data(); + match expn.kind { + // FIXME(@Jarcho): Check that the actual desugaring or std macro is supported by the + // current MSRV. Note that nested expansions need to be handled as well. + ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => return, + ExpnKind::Macro(..) if expn.macro_def_id.is_some_and(|did| self.std_crates.contains(did.krate)) => { + return; + }, + // All other expansions share the target's MSRV. + // FIXME(@Jarcho): What should we do about version dependant macros from external crates? + _ => {}, + } } if (self.check_in_tests || !is_in_test(cx.tcx, node)) diff --git a/src/tools/clippy/clippy_lints/src/inherent_impl.rs b/src/tools/clippy/clippy_lints/src/inherent_impl.rs index a08efbc52d45..f59c7615d745 100644 --- a/src/tools/clippy/clippy_lints/src/inherent_impl.rs +++ b/src/tools/clippy/clippy_lints/src/inherent_impl.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_config::types::InherentImplLintScope; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::fulfill_or_allowed; +use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_in_cfg_test}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::{Item, ItemKind, Node}; @@ -100,7 +100,8 @@ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { }, InherentImplLintScope::Crate => Criterion::Crate, }; - match type_map.entry((impl_ty, criterion)) { + let is_test = is_cfg_test(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id); + match type_map.entry((impl_ty, criterion, is_test)) { Entry::Vacant(e) => { // Store the id for the first impl block of this type. The span is retrieved lazily. e.insert(IdOrSpan::Id(impl_id)); diff --git a/src/tools/clippy/clippy_lints/src/let_if_seq.rs b/src/tools/clippy/clippy_lints/src/let_if_seq.rs index 2dbf55a8540b..dc7a916614be 100644 --- a/src/tools/clippy/clippy_lints/src/let_if_seq.rs +++ b/src/tools/clippy/clippy_lints/src/let_if_seq.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::res::MaybeResPath; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_context; use clippy_utils::visitors::is_local_used; use rustc_errors::Applicability; use rustc_hir as hir; @@ -59,80 +59,88 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { for [stmt, next] in block.stmts.array_windows::<2>() { - if let hir::StmtKind::Let(local) = stmt.kind - && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind - && let hir::StmtKind::Expr(if_) = next.kind - && let hir::ExprKind::If(cond, then, else_) = if_.kind - && !is_local_used(cx, cond, canonical_id) - && let hir::ExprKind::Block(then, _) = then.kind - && let Some(value) = check_assign(cx, canonical_id, then) - && !is_local_used(cx, value, canonical_id) - { - let span = stmt.span.to(if_.span); - - let has_interior_mutability = !cx - .typeck_results() - .node_type(canonical_id) - .is_freeze(cx.tcx, cx.typing_env()); - if has_interior_mutability { - return; - } - - let (default_multi_stmts, default) = if let Some(else_) = else_ { - if let hir::ExprKind::Block(else_, _) = else_.kind { - if let Some(default) = check_assign(cx, canonical_id, else_) { - (else_.stmts.len() > 1, default) - } else if let Some(default) = local.init { - (true, default) - } else { - continue; - } - } else { - continue; - } - } else if let Some(default) = local.init { - (false, default) - } else { - continue; - }; - - let mutability = match mode { - BindingMode(_, Mutability::Mut) => " ", - _ => "", - }; - - // FIXME: this should not suggest `mut` if we can detect that the variable is not - // use mutably after the `if` - - let sug = format!( - "let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};", - name=ident.name, - cond=snippet(cx, cond.span, "_"), - then=if then.stmts.len() > 1 { " ..;" } else { "" }, - else=if default_multi_stmts { " ..;" } else { "" }, - value=snippet(cx, value.span, ""), - default=snippet(cx, default.span, ""), - ); - span_lint_hir_and_then( - cx, - USELESS_LET_IF_SEQ, - local.hir_id, - span, - "`if _ { .. } else { .. }` is an expression", - |diag| { - diag.span_suggestion( - span, - "it is more idiomatic to write", - sug, - Applicability::HasPlaceholders, - ); - if !mutability.is_empty() { - diag.note("you might not need `mut` at all"); - } - }, - ); + if let hir::StmtKind::Expr(if_) = next.kind { + check_block_inner(cx, stmt, if_); } } + + if let Some(expr) = block.expr + && let Some(stmt) = block.stmts.last() + { + check_block_inner(cx, stmt, expr); + } + } +} + +fn check_block_inner<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'tcx>, if_: &'tcx hir::Expr<'tcx>) { + if let hir::StmtKind::Let(local) = stmt.kind + && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind + && let hir::ExprKind::If(cond, then, else_) = if_.kind + && !is_local_used(cx, cond, canonical_id) + && let hir::ExprKind::Block(then, _) = then.kind + && let Some(value) = check_assign(cx, canonical_id, then) + && !is_local_used(cx, value, canonical_id) + { + let span = stmt.span.to(if_.span); + + let has_interior_mutability = !cx + .typeck_results() + .node_type(canonical_id) + .is_freeze(cx.tcx, cx.typing_env()); + if has_interior_mutability { + return; + } + + let (default_multi_stmts, default) = if let Some(else_) = else_ { + if let hir::ExprKind::Block(else_, _) = else_.kind { + if let Some(default) = check_assign(cx, canonical_id, else_) { + (else_.stmts.len() > 1, default) + } else if let Some(default) = local.init { + (true, default) + } else { + return; + } + } else { + return; + } + } else if let Some(default) = local.init { + (false, default) + } else { + return; + }; + + let mutability = match mode { + BindingMode(_, Mutability::Mut) => " ", + _ => "", + }; + + // FIXME: this should not suggest `mut` if we can detect that the variable is not + // use mutably after the `if` + + let mut applicability = Applicability::HasPlaceholders; + let (cond_snip, _) = snippet_with_context(cx, cond.span, if_.span.ctxt(), "_", &mut applicability); + let (value_snip, _) = snippet_with_context(cx, value.span, if_.span.ctxt(), "", &mut applicability); + let (default_snip, _) = + snippet_with_context(cx, default.span, if_.span.ctxt(), "", &mut applicability); + let sug = format!( + "let {mutability}{name} = if {cond_snip} {{{then} {value_snip} }} else {{{else} {default_snip} }};", + name=ident.name, + then=if then.stmts.len() > 1 { " ..;" } else { "" }, + else=if default_multi_stmts { " ..;" } else { "" }, + ); + span_lint_hir_and_then( + cx, + USELESS_LET_IF_SEQ, + local.hir_id, + span, + "`if _ { .. } else { .. }` is an expression", + |diag| { + diag.span_suggestion(span, "it is more idiomatic to write", sug, applicability); + if !mutability.is_empty() { + diag.note("you might not need `mut` at all"); + } + }, + ); } } diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 99cafc7fc6d8..4542105d3277 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -401,7 +401,9 @@ use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; -use rustc_lint::Lint; +use rustc_data_structures::sync; +use rustc_lint::{EarlyLintPass, LateLintPass, Lint}; +use rustc_middle::ty::TyCtxt; use utils::attr_collector::{AttrCollector, AttrStorage}; pub fn explain(name: &str) -> i32 { @@ -443,381 +445,410 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co // level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf))); - store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf))); - let format_args_storage = FormatArgsStorage::default(); - let format_args = format_args_storage.clone(); - store.register_early_pass(move || { - Box::new(utils::format_args_collector::FormatArgsCollector::new( - format_args.clone(), - )) - }); - let attr_storage = AttrStorage::default(); - let attrs = attr_storage.clone(); - store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); - store.register_late_pass(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))); - store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir)); - store.register_late_pass(|_| Box::new(utils::author::Author)); - store.register_late_pass(move |tcx| Box::new(await_holding_invalid::AwaitHolding::new(tcx, conf))); - store.register_late_pass(|_| Box::new(serde_api::SerdeApi)); - store.register_late_pass(move |_| Box::new(types::Types::new(conf))); - store.register_late_pass(move |_| Box::new(booleans::NonminimalBool::new(conf))); - store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant)); - store.register_late_pass(move |_| Box::new(float_literal::FloatLiteral::new(conf))); - store.register_late_pass(|_| Box::new(ptr::Ptr)); - store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool)); - store.register_late_pass(|_| Box::new(bool_comparison::BoolComparison)); - store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach)); - store.register_late_pass(|_| Box::new(misc::LintPass)); - store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); - store.register_late_pass(|_| Box::new(mut_mut::MutMut::default())); - store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)); - store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(move |_| Box::new(len_zero::LenZero::new(conf))); - store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf))); - store.register_late_pass(|_| Box::new(blocks_in_conditions::BlocksInConditions)); - store.register_late_pass(|_| Box::new(unicode::Unicode)); - store.register_late_pass(|_| Box::new(uninit_vec::UninitVec)); - store.register_late_pass(|_| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd)); - store.register_late_pass(|_| Box::new(strings::StringAdd)); - store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn)); - store.register_late_pass(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf))); - store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback)); - store.register_late_pass(move |_| { - Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new( - conf, - )) - }); - store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); - store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); - store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(methods::Methods::new(conf, format_args.clone()))); - store.register_late_pass(move |_| Box::new(matches::Matches::new(conf))); - store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf))); - store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(conf))); - store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(conf))); - store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(conf))); - store.register_late_pass(move |_| Box::new(checked_conversions::CheckedConversions::new(conf))); - store.register_late_pass(move |_| Box::new(mem_replace::MemReplace::new(conf))); - store.register_late_pass(move |_| Box::new(ranges::Ranges::new(conf))); - store.register_late_pass(move |_| Box::new(from_over_into::FromOverInto::new(conf))); - store.register_late_pass(move |_| Box::new(use_self::UseSelf::new(conf))); - store.register_late_pass(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(conf))); - store.register_late_pass(move |_| Box::new(needless_question_mark::NeedlessQuestionMark)); - store.register_late_pass(move |_| Box::new(casts::Casts::new(conf))); - store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(conf))); - store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount)); - store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod)); - store.register_late_pass(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf))); - store.register_late_pass(|_| Box::::default()); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(unit_types::UnitTypes::new(format_args.clone()))); - store.register_late_pass(move |_| Box::new(loops::Loops::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(lifetimes::Lifetimes::new(conf))); - store.register_late_pass(|_| Box::new(entry::HashMapPass)); - store.register_late_pass(|_| Box::new(minmax::MinMaxPass)); - store.register_late_pass(|_| Box::new(zero_div_zero::ZeroDiv)); - store.register_late_pass(|_| Box::new(mutex_atomic::Mutex)); - store.register_late_pass(|_| Box::new(needless_update::NeedlessUpdate)); - store.register_late_pass(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef)); - store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment)); - store.register_late_pass(move |_| Box::new(transmute::Transmute::new(conf))); - store.register_late_pass(move |_| Box::new(cognitive_complexity::CognitiveComplexity::new(conf))); - store.register_late_pass(move |_| Box::new(escape::BoxedLocal::new(conf))); - store.register_late_pass(move |_| Box::new(vec::UselessVec::new(conf))); - store.register_late_pass(move |_| Box::new(panic_unimplemented::PanicUnimplemented::new(conf))); - store.register_late_pass(|_| Box::new(strings::StringLitAsBytes)); - store.register_late_pass(|_| Box::new(derive::Derive)); - store.register_late_pass(move |_| Box::new(derivable_impls::DerivableImpls::new(conf))); - store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef)); - store.register_late_pass(|_| Box::new(empty_enums::EmptyEnums)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))); - store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); - store.register_late_pass(|_| Box::new(swap::Swap)); - store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(conf))); - store.register_early_pass(|| Box::new(functions::EarlyFunctions)); - store.register_late_pass(move |tcx| Box::new(functions::Functions::new(tcx, conf))); - store.register_late_pass(move |_| Box::new(doc::Documentation::new(conf))); - store.register_early_pass(move || Box::new(doc::Documentation::new(conf))); - store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply)); - store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq)); - store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); - store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf))); - store.register_late_pass(|_| Box::new(missing_inline::MissingInline)); - store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems)); - store.register_late_pass(|_| Box::new(unused_result_ok::UnusedResultOk)); - store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk)); - store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); - store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); - store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone()))); - store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)); - store.register_late_pass(move |tcx| Box::new(pass_by_ref_or_value::PassByRefOrValue::new(tcx, conf))); - store.register_late_pass(|_| Box::new(ref_option_ref::RefOptionRef)); - store.register_late_pass(|_| Box::new(infinite_iter::InfiniteIter)); - store.register_late_pass(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(implicit_hasher::ImplicitHasher)); - store.register_late_pass(|_| Box::new(fallible_impl_from::FallibleImplFrom)); - store.register_late_pass(move |_| Box::new(question_mark::QuestionMark::new(conf))); - store.register_late_pass(|_| Box::new(question_mark_used::QuestionMarkUsed)); - store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); - store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)); - store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit)); - store.register_late_pass(move |_| Box::new(inherent_impl::MultipleInherentImpl::new(conf))); - store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); - store.register_late_pass(move |_| Box::new(unwrap::Unwrap::new(conf))); - store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))); - store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))); - store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); - store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit)); - store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))); - store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf))); - store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)); - store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString)); - store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf))); - store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain)); - store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf))); - store.register_late_pass(|_| Box::new(reference::DerefAddrOf)); - store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))); - store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); - store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); - store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); - store.register_early_pass(|| Box::new(formatting::Formatting)); - store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints)); - store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall)); - store.register_early_pass(|| Box::new(unused_unit::UnusedUnit)); - store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit)); - store.register_late_pass(|_| Box::new(returns::Return)); - store.register_late_pass(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf))); - store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements)); - store.register_early_pass(|| Box::new(precedence::Precedence)); - store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); - store.register_late_pass(|_| Box::new(needless_continue::NeedlessContinue)); - store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); - store.register_late_pass(|_| Box::new(create_dir::CreateDir)); - store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); - store.register_early_pass(move || Box::new(literal_representation::LiteralDigitGrouping::new(conf))); - store.register_early_pass(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(conf))); - store.register_late_pass(move |_| Box::new(item_name_repetitions::ItemNameRepetitions::new(conf))); - store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments)); - store.register_late_pass(move |_| Box::new(upper_case_acronyms::UpperCaseAcronyms::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(unused_self::UnusedSelf::new(conf))); - store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall)); - store.register_late_pass(|_| Box::new(exit::Exit)); - store.register_late_pass(move |_| Box::new(to_digit_is_some::ToDigitIsSome::new(conf))); - store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(conf))); - store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(conf))); - store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)); - store.register_late_pass(|_| Box::new(as_conversions::AsConversions)); - store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore)); - store.register_early_pass(|| Box::::default()); - store.register_late_pass(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf))); - store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); - store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse)); - store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend)); - store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(conf))); - store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex)); - store.register_late_pass(|_| Box::new(if_not_else::IfNotElse)); - store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality)); - store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn)); - store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn)); - store.register_early_pass(move || Box::new(non_expressive_names::NonExpressiveNames::new(conf))); - store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); - store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); - let attrs = attr_storage.clone(); - store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))); - store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))); - store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); - store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); - store.register_late_pass(|_| Box::new(empty_drop::EmptyDrop)); - store.register_late_pass(|_| Box::new(strings::StrToString)); - store.register_late_pass(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(redundant_slicing::RedundantSlicing)); - store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10)); - store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))); - store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); - store.register_early_pass(move || Box::new(module_style::ModStyle::default())); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))); - store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))); - store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(conf))); - store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)); - store.register_late_pass(move |_| Box::new(self_named_constructors::SelfNamedConstructors)); - store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)); - store.register_late_pass(move |_| Box::new(manual_assert::ManualAssert)); - store.register_late_pass(move |_| Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(conf))); - store.register_late_pass(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |tcx| Box::new(format_args::FormatArgs::new(tcx, conf, format_args.clone()))); - store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray)); - store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); - store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit)); - store.register_late_pass(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)); - store.register_late_pass(|_| Box::new(init_numbered_fields::NumberedFields)); - store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)); - store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(conf))); - store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(write::Write::new(conf, format_args.clone()))); - store.register_late_pass(move |_| Box::new(cargo::Cargo::new(conf))); - store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef)); - store.register_late_pass(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())); - store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); - store.register_early_pass(|| Box::new(pub_use::PubUse)); - store.register_late_pass(|_| Box::new(format_push_string::FormatPushString)); - store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))); - store.register_early_pass(move || Box::new(large_include_file::LargeIncludeFile::new(conf))); - store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace)); - store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); - store.register_early_pass(|| Box::::default()); - store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding)); - store.register_early_pass(move || Box::new(almost_complete_range::AlmostCompleteRange::new(conf))); - store.register_late_pass(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef)); - store.register_late_pass(|_| Box::new(mismatching_type_param_order::TypeParamMismatch)); - store.register_late_pass(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec)); - store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); - store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(conf))); - store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(conf))); - store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); - store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); - store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); - store.register_late_pass(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf))); - store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); - store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))); - store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); - store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew)); - store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable)); - store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments)); - store.register_late_pass(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf)); - store.register_late_pass(|_| Box::new(box_default::BoxDefault)); - store.register_late_pass(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd)); - store.register_early_pass(|| Box::new(partial_pub_fields::PartialPubFields)); - store.register_late_pass(|_| Box::new(missing_trait_methods::MissingTraitMethods)); - store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr)); - store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)); - store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(conf))); - store.register_late_pass(move |_| Box::new(semicolon_block::SemicolonBlock::new(conf))); - store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)); - store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef)); - store.register_late_pass(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)); - store.register_late_pass(move |_| Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new(conf))); - store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)); - store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)); - store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage)); - store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized)); - store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock)); - store.register_early_pass(|| Box::new(let_with_type_underscore::UnderscoreTyped)); - store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))); - store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); - store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))); - store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); - store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))); - store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))); - store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule)); - store.register_early_pass(|| Box::new(ref_patterns::RefPatterns)); - store.register_late_pass(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs)); - store.register_early_pass(|| Box::new(needless_else::NeedlessElse)); - store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug)); - store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes)); - store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)); - store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)); - store.register_late_pass(|_| Box::new(needless_ifs::NeedlessIfs)); - store.register_late_pass(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf))); - store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf))); - store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)); - store.register_late_pass(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf))); - store.register_late_pass(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx))); - store.register_late_pass(move |_| Box::new(single_call_fn::SingleCallFn::new(conf))); - store.register_early_pass(move || Box::new(raw_strings::RawStrings::new(conf))); - store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf))); - store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns)); - store.register_early_pass(|| Box::new(visibility::Visibility)); - store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions::new(conf))); - store.register_late_pass(move |_| Box::new(manual_float_methods::ManualFloatMethods::new(conf))); - store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); - store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError)); - store.register_late_pass(move |_| Box::new(absolute_paths::AbsolutePaths::new(conf))); - store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals)); - store.register_late_pass(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls)); - store.register_late_pass(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing)); - store.register_late_pass(|_| Box::new(unnecessary_map_on_constructor::UnnecessaryMapOnConstructor)); - store.register_late_pass(move |_| { - Box::new(needless_borrows_for_generic_args::NeedlessBorrowsForGenericArgs::new( - conf, - )) - }); - store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(conf))); - store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter)); - store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(|_| Box::new(iter_over_hash_type::IterOverHashType)); - store.register_late_pass(|_| Box::new(impl_hash_with_borrow_str_and_bytes::ImplHashWithBorrowStrBytes)); - store.register_late_pass(move |_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity::new(conf))); - store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences)); - store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(pub_underscore_fields::PubUnderscoreFields::new(conf))); - store.register_late_pass(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(conf))); - store.register_late_pass(move |tcx| Box::new(incompatible_msrv::IncompatibleMsrv::new(tcx, conf))); - store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); - store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); - store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); - store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); - store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); - store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); - store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); - store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)); - store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); - store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); - store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses)); - store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); - store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))); - store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); - store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)); - 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(useless_concat::UselessConcat)); - store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); - store.register_late_pass(|_| Box::::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))); - store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap)); - store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)); - store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); - store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); - store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); - store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); - store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites)); - store.register_late_pass(|_| Box::new(replace_box::ReplaceBox)); - // add lints here, do not remove this comment, it's used in `new_lint` + let early_lints: [Box Box + sync::DynSend + sync::DynSync>; _] = [ + { + let format_args = format_args_storage.clone(); + Box::new(move || { + Box::new(utils::format_args_collector::FormatArgsCollector::new( + format_args.clone(), + )) + }) + }, + { + let attrs = attr_storage.clone(); + Box::new(move || Box::new(AttrCollector::new(attrs.clone()))) + }, + Box::new(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf))), + Box::new(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)), + Box::new(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(conf))), + Box::new(move || Box::new(redundant_field_names::RedundantFieldNames::new(conf))), + Box::new(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(conf))), + Box::new(|| Box::new(functions::EarlyFunctions)), + Box::new(move || Box::new(doc::Documentation::new(conf))), + Box::new(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)), + Box::new(|| Box::new(double_parens::DoubleParens)), + Box::new(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)), + Box::new(|| Box::new(else_if_without_else::ElseIfWithoutElse)), + Box::new(|| Box::new(int_plus_one::IntPlusOne)), + Box::new(|| Box::new(formatting::Formatting)), + Box::new(|| Box::new(misc_early::MiscEarlyLints)), + Box::new(|| Box::new(unused_unit::UnusedUnit)), + Box::new(|| Box::new(precedence::Precedence)), + Box::new(|| Box::new(redundant_else::RedundantElse)), + Box::new(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)), + Box::new(move || Box::new(literal_representation::LiteralDigitGrouping::new(conf))), + Box::new(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(conf))), + Box::new(|| Box::new(tabs_in_doc_comments::TabsInDocComments)), + Box::new(|| Box::::default()), + Box::new(|| Box::new(option_env_unwrap::OptionEnvUnwrap)), + Box::new(move || Box::new(non_expressive_names::NonExpressiveNames::new(conf))), + Box::new(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))), + Box::new(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)), + Box::new(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)), + Box::new(move || Box::new(module_style::ModStyle::default())), + Box::new(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(conf))), + Box::new(|| Box::new(octal_escapes::OctalEscapes)), + Box::new(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)), + Box::new(|| Box::new(crate_in_macro_def::CrateInMacroDef)), + Box::new(|| Box::new(pub_use::PubUse)), + Box::new(move || Box::new(large_include_file::LargeIncludeFile::new(conf))), + Box::new(|| Box::::default()), + Box::new(|| Box::new(unused_rounding::UnusedRounding)), + Box::new(move || Box::new(almost_complete_range::AlmostCompleteRange::new(conf))), + Box::new(|| Box::new(multi_assignments::MultiAssignments)), + Box::new(|| Box::new(partial_pub_fields::PartialPubFields)), + Box::new(|| Box::new(let_with_type_underscore::UnderscoreTyped)), + Box::new(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))), + Box::new(|| Box::new(ref_patterns::RefPatterns)), + Box::new(|| Box::new(needless_else::NeedlessElse)), + Box::new(move || Box::new(raw_strings::RawStrings::new(conf))), + Box::new(|| Box::new(visibility::Visibility)), + Box::new(|| Box::new(multiple_bound_locations::MultipleBoundLocations)), + Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)), + Box::new(|| Box::new(byte_char_slices::ByteCharSlice)), + Box::new(|| Box::new(cfg_not_test::CfgNotTest)), + Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())), + // add early passes here, used by `cargo dev new_lint` + ]; + store.early_passes.extend(early_lints); + + #[expect(clippy::type_complexity)] + let late_lints: [Box< + dyn for<'tcx> Fn(TyCtxt<'tcx>) -> Box + 'tcx> + sync::DynSend + sync::DynSync, + >; _] = [ + Box::new(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))), + Box::new(|_| Box::new(utils::dump_hir::DumpHir)), + Box::new(|_| Box::new(utils::author::Author)), + Box::new(move |tcx| Box::new(await_holding_invalid::AwaitHolding::new(tcx, conf))), + Box::new(|_| Box::new(serde_api::SerdeApi)), + Box::new(move |_| Box::new(types::Types::new(conf))), + Box::new(move |_| Box::new(booleans::NonminimalBool::new(conf))), + Box::new(|_| Box::new(enum_clike::UnportableVariant)), + Box::new(move |_| Box::new(float_literal::FloatLiteral::new(conf))), + Box::new(|_| Box::new(ptr::Ptr)), + Box::new(|_| Box::new(needless_bool::NeedlessBool)), + Box::new(|_| Box::new(bool_comparison::BoolComparison)), + Box::new(|_| Box::new(needless_for_each::NeedlessForEach)), + Box::new(|_| Box::new(misc::LintPass)), + Box::new(|_| Box::new(eta_reduction::EtaReduction)), + Box::new(|_| Box::new(mut_mut::MutMut::default())), + Box::new(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)), + Box::new(|_| Box::>::default()), + Box::new(move |_| Box::new(len_zero::LenZero::new(conf))), + Box::new(move |_| Box::new(attrs::Attributes::new(conf))), + Box::new(|_| Box::new(blocks_in_conditions::BlocksInConditions)), + Box::new(|_| Box::new(unicode::Unicode)), + Box::new(|_| Box::new(uninit_vec::UninitVec)), + Box::new(|_| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd)), + Box::new(|_| Box::new(strings::StringAdd)), + Box::new(|_| Box::new(implicit_return::ImplicitReturn)), + Box::new(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf))), + Box::new(|_| Box::new(default_numeric_fallback::DefaultNumericFallback)), + Box::new(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)), + Box::new(move |_| Box::new(approx_const::ApproxConstant::new(conf))), + Box::new(move |_| Box::new(matches::Matches::new(conf))), + Box::new(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf))), + Box::new(move |_| Box::new(manual_strip::ManualStrip::new(conf))), + Box::new(move |_| Box::new(checked_conversions::CheckedConversions::new(conf))), + Box::new(move |_| Box::new(mem_replace::MemReplace::new(conf))), + Box::new(move |_| Box::new(ranges::Ranges::new(conf))), + Box::new(move |_| Box::new(from_over_into::FromOverInto::new(conf))), + Box::new(move |_| Box::new(use_self::UseSelf::new(conf))), + Box::new(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(conf))), + Box::new(move |_| Box::new(needless_question_mark::NeedlessQuestionMark)), + Box::new(move |_| Box::new(casts::Casts::new(conf))), + Box::new(|_| Box::new(size_of_in_element_count::SizeOfInElementCount)), + Box::new(|_| Box::new(same_name_method::SameNameMethod)), + Box::new(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf))), + Box::new(|_| Box::::default()), + Box::new(move |_| { + Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new( + conf, + )) + }), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(methods::Methods::new(conf, format_args.clone()))) + }, + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(unit_types::UnitTypes::new(format_args.clone()))) + }, + Box::new(move |_| Box::new(loops::Loops::new(conf))), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(lifetimes::Lifetimes::new(conf))), + Box::new(|_| Box::new(entry::HashMapPass)), + Box::new(|_| Box::new(minmax::MinMaxPass)), + Box::new(|_| Box::new(zero_div_zero::ZeroDiv)), + Box::new(|_| Box::new(mutex_atomic::Mutex)), + Box::new(|_| Box::new(needless_update::NeedlessUpdate)), + Box::new(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef)), + Box::new(|_| Box::new(borrow_deref_ref::BorrowDerefRef)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(temporary_assignment::TemporaryAssignment)), + Box::new(move |_| Box::new(transmute::Transmute::new(conf))), + Box::new(move |_| Box::new(cognitive_complexity::CognitiveComplexity::new(conf))), + Box::new(move |_| Box::new(escape::BoxedLocal::new(conf))), + Box::new(move |_| Box::new(vec::UselessVec::new(conf))), + Box::new(move |_| Box::new(panic_unimplemented::PanicUnimplemented::new(conf))), + Box::new(|_| Box::new(strings::StringLitAsBytes)), + Box::new(|_| Box::new(derive::Derive)), + Box::new(move |_| Box::new(derivable_impls::DerivableImpls::new(conf))), + Box::new(|_| Box::new(drop_forget_ref::DropForgetRef)), + Box::new(|_| Box::new(empty_enums::EmptyEnums)), + Box::new(|_| Box::::default()), + Box::new(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))), + Box::new(|_| Box::new(copy_iterator::CopyIterator)), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(format::UselessFormat::new(format_args.clone()))) + }, + Box::new(|_| Box::new(swap::Swap)), + Box::new(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(disallowed_names::DisallowedNames::new(conf))), + Box::new(move |tcx| Box::new(functions::Functions::new(tcx, conf))), + Box::new(move |_| Box::new(doc::Documentation::new(conf))), + Box::new(|_| Box::new(neg_multiply::NegMultiply)), + Box::new(|_| Box::new(let_if_seq::LetIfSeq)), + Box::new(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence)), + Box::new(move |_| Box::new(missing_doc::MissingDoc::new(conf))), + Box::new(|_| Box::new(missing_inline::MissingInline)), + Box::new(move |_| Box::new(exhaustive_items::ExhaustiveItems)), + Box::new(|_| Box::new(unused_result_ok::UnusedResultOk)), + Box::new(|_| Box::new(match_result_ok::MatchResultOk)), + Box::new(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)), + Box::new(|_| Box::new(unused_io_amount::UnusedIoAmount)), + Box::new(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone()))) + }, + Box::new(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)), + Box::new(move |tcx| Box::new(pass_by_ref_or_value::PassByRefOrValue::new(tcx, conf))), + Box::new(|_| Box::new(ref_option_ref::RefOptionRef)), + Box::new(|_| Box::new(infinite_iter::InfiniteIter)), + Box::new(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(implicit_hasher::ImplicitHasher)), + Box::new(|_| Box::new(fallible_impl_from::FallibleImplFrom)), + Box::new(move |_| Box::new(question_mark::QuestionMark::new(conf))), + Box::new(|_| Box::new(question_mark_used::QuestionMarkUsed)), + Box::new(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)), + Box::new(|_| Box::new(map_unit_fn::MapUnit)), + Box::new(move |_| Box::new(inherent_impl::MultipleInherentImpl::new(conf))), + Box::new(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)), + Box::new(move |_| Box::new(unwrap::Unwrap::new(conf))), + Box::new(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))), + Box::new(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))), + Box::new(|_| Box::new(redundant_clone::RedundantClone)), + Box::new(|_| Box::new(slow_vector_initialization::SlowVectorInit)), + Box::new(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))), + Box::new(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf))), + Box::new(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)), + Box::new(|_| Box::new(inherent_to_string::InherentToString)), + Box::new(move |_| Box::new(trait_bounds::TraitBounds::new(conf))), + Box::new(|_| Box::new(comparison_chain::ComparisonChain)), + Box::new(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf))), + Box::new(|_| Box::new(reference::DerefAddrOf)), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))) + }, + Box::new(|_| Box::new(redundant_closure_call::RedundantClosureCall)), + Box::new(|_| Box::new(unused_unit::UnusedUnit)), + Box::new(|_| Box::new(returns::Return)), + Box::new(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf))), + Box::new(|_| Box::new(items_after_statements::ItemsAfterStatements)), + Box::new(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)), + Box::new(|_| Box::new(needless_continue::NeedlessContinue)), + Box::new(|_| Box::new(create_dir::CreateDir)), + Box::new(move |_| Box::new(item_name_repetitions::ItemNameRepetitions::new(conf))), + Box::new(move |_| Box::new(upper_case_acronyms::UpperCaseAcronyms::new(conf))), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(unused_self::UnusedSelf::new(conf))), + Box::new(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall)), + Box::new(|_| Box::new(exit::Exit)), + Box::new(move |_| Box::new(to_digit_is_some::ToDigitIsSome::new(conf))), + Box::new(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(conf))), + Box::new(move |_| Box::new(large_const_arrays::LargeConstArrays::new(conf))), + Box::new(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)), + Box::new(|_| Box::new(as_conversions::AsConversions)), + Box::new(|_| Box::new(let_underscore::LetUnderscore)), + Box::new(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf))), + Box::new(move |_| Box::new(wildcard_imports::WildcardImports::new(conf))), + Box::new(|_| Box::::default()), + Box::new(|_| Box::>::default()), + Box::new(|_| Box::new(option_if_let_else::OptionIfLetElse)), + Box::new(|_| Box::new(future_not_send::FutureNotSend)), + Box::new(move |_| Box::new(large_futures::LargeFuture::new(conf))), + Box::new(|_| Box::new(if_let_mutex::IfLetMutex)), + Box::new(|_| Box::new(if_not_else::IfNotElse)), + Box::new(|_| Box::new(equatable_if_let::PatternEquality)), + Box::new(|_| Box::new(manual_async_fn::ManualAsyncFn)), + Box::new(|_| Box::new(panic_in_result_fn::PanicInResultFn)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)), + Box::new(|_| Box::new(async_yields_async::AsyncYieldsAsync)), + { + let attrs = attr_storage.clone(); + Box::new(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))) + }, + Box::new(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))), + Box::new(|_| Box::new(empty_drop::EmptyDrop)), + Box::new(|_| Box::new(strings::StrToString)), + Box::new(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(redundant_slicing::RedundantSlicing)), + Box::new(|_| Box::new(from_str_radix_10::FromStrRadix10)), + Box::new(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))), + Box::new(|_| Box::new(bool_assert_comparison::BoolAssertComparison)), + Box::new(|_| Box::::default()), + Box::new(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))), + Box::new(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))), + Box::new(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)), + Box::new(move |_| Box::new(self_named_constructors::SelfNamedConstructors)), + Box::new(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)), + Box::new(move |_| Box::new(manual_assert::ManualAssert)), + Box::new(move |_| Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(conf))), + Box::new(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |tcx| Box::new(format_args::FormatArgs::new(tcx, conf, format_args.clone()))) + }, + Box::new(|_| Box::new(trailing_empty_array::TrailingEmptyArray)), + Box::new(|_| Box::new(needless_late_init::NeedlessLateInit)), + Box::new(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)), + Box::new(|_| Box::new(init_numbered_fields::NumberedFields)), + Box::new(move |_| Box::new(manual_bits::ManualBits::new(conf))), + Box::new(|_| Box::new(default_union_representation::DefaultUnionRepresentation)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(dbg_macro::DbgMacro::new(conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(write::Write::new(conf, format_args.clone()))) + }, + Box::new(move |_| Box::new(cargo::Cargo::new(conf))), + Box::new(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())), + Box::new(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)), + Box::new(|_| Box::new(format_push_string::FormatPushString)), + Box::new(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))), + Box::new(|_| Box::new(strings::TrimSplitWhitespace)), + Box::new(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)), + Box::new(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef)), + Box::new(|_| Box::new(mismatching_type_param_order::TypeParamMismatch)), + Box::new(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec)), + Box::new(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)), + Box::new(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(conf))), + Box::new(move |_| Box::new(manual_retain::ManualRetain::new(conf))), + Box::new(move |_| Box::new(manual_rotate::ManualRotate)), + Box::new(move |_| Box::new(operators::Operators::new(conf))), + Box::new(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))), + Box::new(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf))), + Box::new(|_| Box::new(partialeq_to_none::PartialeqToNone)), + Box::new(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))), + Box::new(move |_| Box::new(manual_clamp::ManualClamp::new(conf))), + Box::new(|_| Box::new(manual_string_new::ManualStringNew)), + Box::new(|_| Box::new(unused_peekable::UnusedPeekable)), + Box::new(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf)), + Box::new(|_| Box::new(box_default::BoxDefault)), + Box::new(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd)), + Box::new(|_| Box::new(missing_trait_methods::MissingTraitMethods)), + Box::new(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr)), + Box::new(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)), + Box::new(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(conf))), + Box::new(move |_| Box::new(semicolon_block::SemicolonBlock::new(conf))), + Box::new(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)), + Box::new(|_| Box::new(size_of_ref::SizeOfRef)), + Box::new(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)), + Box::new(move |_| Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new(conf))), + Box::new(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)), + Box::new(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)), + Box::new(|_| Box::new(missing_assert_message::MissingAssertMessage)), + Box::new(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized)), + Box::new(|_| Box::new(redundant_async_block::RedundantAsyncBlock)), + Box::new(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))), + Box::new(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)), + Box::new(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))), + Box::new(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)), + Box::new(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))), + Box::new(|_| Box::new(items_after_test_module::ItemsAfterTestModule)), + Box::new(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs)), + Box::new(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug)), + Box::new(|_| Box::new(endian_bytes::EndianBytes)), + Box::new(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)), + Box::new(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)), + Box::new(|_| Box::new(needless_ifs::NeedlessIfs)), + Box::new(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf))), + Box::new(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf))), + Box::new(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)), + Box::new(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf))), + Box::new(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx))), + Box::new(move |_| Box::new(single_call_fn::SingleCallFn::new(conf))), + Box::new(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf))), + Box::new(|_| Box::new(manual_range_patterns::ManualRangePatterns)), + Box::new(move |_| Box::new(tuple_array_conversions::TupleArrayConversions::new(conf))), + Box::new(move |_| Box::new(manual_float_methods::ManualFloatMethods::new(conf))), + Box::new(|_| Box::new(four_forward_slashes::FourForwardSlashes)), + Box::new(|_| Box::new(error_impl_error::ErrorImplError)), + Box::new(move |_| Box::new(absolute_paths::AbsolutePaths::new(conf))), + Box::new(|_| Box::new(redundant_locals::RedundantLocals)), + Box::new(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls)), + Box::new(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing)), + Box::new(|_| Box::new(unnecessary_map_on_constructor::UnnecessaryMapOnConstructor)), + Box::new(move |_| { + Box::new(needless_borrows_for_generic_args::NeedlessBorrowsForGenericArgs::new( + conf, + )) + }), + Box::new(move |_| Box::new(manual_hash_one::ManualHashOne::new(conf))), + Box::new(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter)), + Box::new(|_| Box::>::default()), + Box::new(|_| Box::new(iter_over_hash_type::IterOverHashType)), + Box::new(|_| Box::new(impl_hash_with_borrow_str_and_bytes::ImplHashWithBorrowStrBytes)), + Box::new(move |_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity::new(conf))), + Box::new(|_| Box::new(uninhabited_references::UninhabitedReferences)), + Box::new(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(pub_underscore_fields::PubUnderscoreFields::new(conf))), + Box::new(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(conf))), + Box::new(move |tcx| Box::new(incompatible_msrv::IncompatibleMsrv::new(tcx, conf))), + Box::new(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)), + Box::new(move |_| Box::new(assigning_clones::AssigningClones::new(conf))), + Box::new(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)), + Box::new(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))), + Box::new(move |_| Box::new(string_patterns::StringPatterns::new(conf))), + Box::new(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)), + Box::new(|_| Box::new(zombie_processes::ZombieProcesses)), + Box::new(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)), + Box::new(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))), + Box::new(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)), + Box::new(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)), + Box::new(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))), + Box::new(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)), + Box::new(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)), + Box::new(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))), + Box::new(|_| Box::new(useless_concat::UselessConcat)), + Box::new(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))), + Box::new(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf))), + Box::new(|_| Box::new(single_option_map::SingleOptionMap)), + Box::new(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)), + Box::new(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))), + Box::new(|_| Box::new(infallible_try_from::InfallibleTryFrom)), + Box::new(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)), + Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)), + Box::new(|_| Box::new(volatile_composites::VolatileComposites)), + Box::new(|_| Box::::default()), + // add late passes here, used by `cargo dev new_lint` + ]; + store.late_passes.extend(late_lints); } diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_find.rs b/src/tools/clippy/clippy_lints/src/loops/manual_find.rs index 3455a47ba078..d94dcfab23c7 100644 --- a/src/tools/clippy/clippy_lints/src/loops/manual_find.rs +++ b/src/tools/clippy/clippy_lints/src/loops/manual_find.rs @@ -5,7 +5,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{higher, peel_blocks_with_stmt}; +use clippy_utils::{as_some_expr, higher, peel_blocks_with_stmt}; use rustc_errors::Applicability; use rustc_hir::lang_items::LangItem; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind}; @@ -33,8 +33,7 @@ pub(super) fn check<'tcx>( && let [stmt] = block.stmts && let StmtKind::Semi(semi) = stmt.kind && let ExprKind::Ret(Some(ret_value)) = semi.kind - && let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind - && ctor.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(inner_ret) = as_some_expr(cx, ret_value) && inner_ret.res_local_id() == Some(binding_id) && !contains_return_break_continue_macro(cond) && let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr) diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs index a064a5910ef9..21198c3c8bc2 100644 --- a/src/tools/clippy/clippy_lints/src/loops/mod.rs +++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs @@ -880,6 +880,15 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } + + if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind + && matches!( + path.ident.name, + sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map + ) + { + unused_enumerate_index::check_method(cx, expr, recv, arg); + } } } @@ -908,7 +917,7 @@ fn check_for_loop<'tcx>( same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check(cx, pat, arg, body); + unused_enumerate_index::check(cx, arg, pat, None, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs b/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs index b893b0baad49..82ded453616d 100644 --- a/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,42 +1,87 @@ use super::UNUSED_ENUMERATE_INDEX; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet; -use clippy_utils::{pat_is_wild, sugg}; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; +use clippy_utils::{expr_or_init, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_span::sym; +use rustc_span::{Span, SyntaxContext, sym}; -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. -/// -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { - if let PatKind::Tuple([index, elem], _) = pat.kind - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind - && let ty = cx.typeck_results().expr_ty(arg) - && pat_is_wild(cx, &index.kind, body) - && ty.is_diag_item(cx, sym::Enumerate) - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + iter_expr: &'tcx Expr<'tcx>, + pat: &Pat<'tcx>, + ty_spans: Option<(Span, Span)>, + body: &'tcx Expr<'tcx>, +) { + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind + && cx.typeck_results().expr_ty(iter_expr).is_diag_item(cx, sym::Enumerate) + && pat_is_wild(cx, &idx_pat.kind, body) + && let enumerate_call = expr_or_init(cx, iter_expr) + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) + && !enumerate_call.span.from_expansion() + && !pat.span.from_expansion() + && !idx_pat.span.from_expansion() + && !inner_pat.span.from_expansion() + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { + text.get(..range.start)? + .ends_with('.') + .then_some(range.start - 1..range.end) + }) { - span_lint_and_then( + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); + span_lint_hir_and_then( cx, UNUSED_ENUMERATE_INDEX, - arg.span, + enumerate_call.hir_id, + enumerate_span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); + let mut spans = Vec::with_capacity(5); + spans.push((enumerate_span, String::new())); + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); + if let Some((outer, inner)) = ty_spans { + spans.push((outer.with_hi(inner.lo()), String::new())); + spans.push((outer.with_lo(inner.hi()), String::new())); + } diag.multipart_suggestion( "remove the `.enumerate()` call", - vec![ - (pat.span, snippet(cx, elem.span, "..").into_owned()), - (arg.span, base_iter.to_string()), - ], + spans, Applicability::MachineApplicable, ); }, ); } } + +pub(super) fn check_method<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + arg: &'tcx Expr<'tcx>, +) { + if let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + && cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let [input] = closure.fn_decl.inputs + && !arg.span.from_expansion() + && !input.span.from_expansion() + && !recv.span.from_expansion() + && !param.span.from_expansion() + { + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { + return; + }; + Some((input.span, inner)) + } else { + None + }; + check(cx, recv, param.pat, ty_spans, body.value); + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs b/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs index 3ea6ba341bed..2545f81f1afa 100644 --- a/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs @@ -5,11 +5,11 @@ use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::visitors::is_res_used; -use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; +use clippy_utils::{as_some_pattern, get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Closure, Expr, ExprKind, HirId, LangItem, LetStmt, Mutability, PatKind, UnOp}; +use rustc_hir::{Closure, Expr, ExprKind, HirId, LetStmt, Mutability, UnOp}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::ty::adjustment::Adjust; @@ -19,8 +19,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::WhileLet { if_then, let_pat, let_expr, label, .. }) = higher::WhileLet::hir(expr) // check for `Some(..)` pattern - && let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind - && cx.qpath_res(pat_path, let_pat.hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(some_pat) = as_some_pattern(cx, let_pat) // check for call to `Iterator::next` && let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind && method_name.ident.name == sym::next diff --git a/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs b/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs index 4439a28763a2..25db719c8214 100644 --- a/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs +++ b/src/tools/clippy/clippy_lints/src/manual_is_power_of_two.rs @@ -70,12 +70,12 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if !expr.span.from_expansion() && let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq) { - if let Some(a) = count_ones_receiver(cx, lhs) - && is_integer_literal(rhs, 1) + if is_integer_literal(rhs, 1) + && let Some(a) = count_ones_receiver(cx, lhs) { self.build_sugg(cx, expr, a); - } else if let Some(a) = count_ones_receiver(cx, rhs) - && is_integer_literal(lhs, 1) + } else if is_integer_literal(lhs, 1) + && let Some(a) = count_ones_receiver(cx, rhs) { self.build_sugg(cx, expr, a); } else if is_integer_literal(rhs, 0) 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 index dce0d105f4c5..5cf90eecaa97 100644 --- a/src/tools/clippy/clippy_lints/src/manual_option_as_slice.rs +++ b/src/tools/clippy/clippy_lints/src/manual_option_as_slice.rs @@ -3,10 +3,9 @@ use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_context; -use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym}; +use clippy_utils::{as_some_pattern, is_none_pattern, msrvs, peel_hir_expr_refs, sym}; 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_hir::{Arm, Expr, ExprKind, Pat, PatKind, QPath, is_range_literal}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; @@ -154,10 +153,8 @@ fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { } fn extract_ident_from_some_pat(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option { - if let PatKind::TupleStruct(QPath::Resolved(None, path), [binding], _) = pat.kind - && let Res::Def(DefKind::Ctor(..), def_id) = path.res + if let Some([binding]) = as_some_pattern(cx, pat) && 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 { diff --git a/src/tools/clippy/clippy_lints/src/match_result_ok.rs b/src/tools/clippy/clippy_lints/src/match_result_ok.rs index fb83f7cf65dd..1ebbd209ae52 100644 --- a/src/tools/clippy/clippy_lints/src/match_result_ok.rs +++ b/src/tools/clippy/clippy_lints/src/match_result_ok.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::{higher, sym}; +use clippy_utils::{as_some_pattern, higher, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -55,10 +55,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { }; if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result.ok(, _) - && let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation && ok_path.ident.name == sym::ok && cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - && cx.qpath_res(pat_path, let_pat.hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some([ok_pat]) = as_some_pattern(cx, let_pat) //get operation && let ctxt = expr.span.ctxt() && let_expr.span.ctxt() == ctxt && let_pat.span.ctxt() == ctxt diff --git a/src/tools/clippy/clippy_lints/src/matches/manual_filter.rs b/src/tools/clippy/clippy_lints/src/matches/manual_filter.rs index d7224052ebc5..da68f8421c16 100644 --- a/src/tools/clippy/clippy_lints/src/matches/manual_filter.rs +++ b/src/tools/clippy/clippy_lints/src/matches/manual_filter.rs @@ -1,8 +1,9 @@ +use clippy_utils::as_some_expr; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::visitors::contains_unsafe_block; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionNone; use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -52,21 +53,19 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> { peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr) } -// function called for each expression: +/// Checks whether resolves to `Some(target)` +// NOTE: called for each expression: // Some(x) => if { // // } else { // // } -// Returns true if resolves to `Some(x)`, `false` otherwise fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) // there can be not statements in the block as they would be removed when switching to `.filter` - && let ExprKind::Call(callee, [arg]) = inner_expr.kind + && let Some(arg) = as_some_expr(cx, inner_expr) { - return ctxt == expr.span.ctxt() - && callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && arg.res_local_id() == Some(target); + return ctxt == expr.span.ctxt() && arg.res_local_id() == Some(target); } false } diff --git a/src/tools/clippy/clippy_lints/src/matches/manual_ok_err.rs b/src/tools/clippy/clippy_lints/src/matches/manual_ok_err.rs index 9ced6c9d452b..c35c3d1f62e6 100644 --- a/src/tools/clippy/clippy_lints/src/matches/manual_ok_err.rs +++ b/src/tools/clippy/clippy_lints/src/matches/manual_ok_err.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{option_arg_ty, peel_and_count_ty_refs}; -use clippy_utils::{get_parent_expr, peel_blocks, span_contains_comment}; +use clippy_utils::{as_some_expr, get_parent_expr, is_none_expr, peel_blocks, span_contains_comment}; use rustc_ast::{BindingMode, Mutability}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; use rustc_lint::{LateContext, LintContext}; @@ -106,8 +106,7 @@ fn is_ok_or_err<'hir>(cx: &LateContext<'_>, pat: &Pat<'hir>) -> Option<(bool, &' /// Check if `expr` contains `Some(ident)`, possibly as a block fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, ty: Ty<'tcx>) -> bool { - if let ExprKind::Call(body_callee, [body_arg]) = peel_blocks(expr).kind - && body_callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + if let Some(body_arg) = as_some_expr(cx, peel_blocks(expr)) && cx.typeck_results().expr_ty(body_arg) == ty && let ExprKind::Path(QPath::Resolved( _, @@ -124,7 +123,7 @@ fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, t /// Check if `expr` is `None`, possibly as a block fn is_none(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + is_none_expr(cx, peel_blocks(expr)) } /// Suggest replacing `expr` by `scrutinee.METHOD()`, where `METHOD` is either `ok` or diff --git a/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs b/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs index 19b3572bd3ae..6a755fac45fe 100644 --- a/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs +++ b/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs @@ -1,18 +1,17 @@ use crate::map_unit_fn::OPTION_MAP_UNIT_FN; use crate::matches::MATCH_AS_REF; -use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_copy, is_unsafe_fn, peel_and_count_ty_refs}; use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, peel_blocks, - peel_hir_expr_refs, peel_hir_expr_while, + CaptureKind, as_some_pattern, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, + is_none_expr, is_none_pattern, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::def::Res; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -44,16 +43,16 @@ pub(super) fn check_with<'tcx, F>( try_parse_pattern(cx, then_pat, expr_ctxt), else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)), ) { - (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, true) }, - (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, false) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, true) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, false) }, _ => return None, @@ -255,23 +254,9 @@ fn f<'tcx>( match pat.kind { PatKind::Wild => Some(OptionPat::Wild), PatKind::Ref(pat, _, _) => f(cx, pat, ref_count + 1, ctxt), - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) if cx - .qpath_res(qpath, *hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - Some(OptionPat::None) - }, - PatKind::TupleStruct(ref qpath, [pattern], _) - if cx - .qpath_res(qpath, pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionSome) - && pat.span.ctxt() == ctxt => + _ if is_none_pattern(cx, pat) => Some(OptionPat::None), + _ if let Some([pattern]) = as_some_pattern(cx, pat) + && pat.span.ctxt() == ctxt => { Some(OptionPat::Some { pattern, ref_count }) }, @@ -281,7 +266,7 @@ fn f<'tcx>( f(cx, pat, 0, ctxt) } -// Checks for the `None` value. -fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +/// Checks for the `None` value, possibly in a block. +fn is_none_arm_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + is_none_expr(cx, peel_blocks(expr)) } 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 2ca656edc66e..795355f25f9e 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,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::option_arg_ty; -use clippy_utils::{is_none_arm, peel_blocks}; +use clippy_utils::{as_some_expr, as_some_pattern, is_none_arm, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; +use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, Mutability, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -82,14 +81,9 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { - if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind - && cx - .qpath_res(qpath, arm.pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, LangItem::OptionSome) + if let Some([first_pat, ..]) = as_some_pattern(cx, arm.pat) && let PatKind::Binding(BindingMode(ByRef::Yes(_, mutabl), _), .., ident, _) = first_pat.kind - && let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind - && e.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(arg) = as_some_expr(cx, peel_blocks(arm.body)) && let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind && path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name diff --git a/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs b/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs index 82d5310663ee..e40e21c490f3 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs @@ -8,7 +8,7 @@ use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_path, walk_stmt}; -use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Node, PatKind, Path, Stmt, StmtKind}; +use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, PatKind, Path, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; @@ -307,26 +307,6 @@ fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { false } -fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { - let parent = cx.tcx.parent_hir_node(match_expr.hir_id); - if let Node::Expr(Expr { - kind: ExprKind::Closure(..) | ExprKind::Binary(..), - .. - }) - | Node::AnonConst(..) = parent - { - return true; - } - - if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) - && let ExprKind::Match(..) = arm.body.kind - { - return true; - } - - false -} - fn indent_of_nth_line(snippet: &str, nth: usize) -> Option { snippet .lines() @@ -379,14 +359,47 @@ fn sugg_with_curlies<'a>( let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); - if !expr_in_nested_block(cx, match_expr) - && ((needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies(cx, match_expr)) - { + let mut add_curlies = || { cbrace_end = format!("\n{indent}}}"); // Fix body indent due to the closure indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); cbrace_start = format!("{{\n{indent}"); snippet_body = reindent_snippet_if_in_block(&snippet_body, !assignment_str.is_empty()); + }; + + if !expr_in_nested_block(cx, match_expr) { + let mut parent = cx.tcx.parent_hir_node(match_expr.hir_id); + if let Node::Expr(Expr { + kind: ExprKind::Assign(..), + hir_id, + .. + }) = parent + { + parent = cx.tcx.parent_hir_node(*hir_id); + } + if let Node::Stmt(stmt) = parent { + parent = cx.tcx.parent_hir_node(stmt.hir_id); + } + + match parent { + Node::Block(..) + | Node::Expr(Expr { + kind: ExprKind::Block(..) | ExprKind::ConstBlock(..), + .. + }) => { + if needs_var_binding && is_var_binding_used_later { + add_curlies(); + } + }, + Node::Expr(..) + | Node::AnonConst(..) + | Node::Item(Item { + kind: ItemKind::Const(..), + .. + }) => add_curlies(), + Node::Arm(arm) if let ExprKind::Match(..) = arm.body.kind => add_curlies(), + _ => {}, + } } format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}") diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 81fecc87256c..bac35e7f8c70 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{first_line_of_span, indent_of, snippet}; use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; -use clippy_utils::{get_attr, is_lint_allowed, sym}; +use clippy_utils::{get_builtin_attr, is_lint_allowed, sym}; use itertools::Itertools; use rustc_ast::Mutability; use rustc_data_structures::fx::FxIndexSet; @@ -183,7 +183,7 @@ fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool { if let Some(adt) = ty.ty_adt_def() - && get_attr( + && get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/src/tools/clippy/clippy_lints/src/matches/single_match.rs b/src/tools/clippy/clippy_lints/src/matches/single_match.rs index 57a91cf846b8..8642c7e349b1 100644 --- a/src/tools/clippy/clippy_lints/src/matches/single_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/single_match.rs @@ -373,7 +373,10 @@ fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool { }, // Patterns for things which can only contain a single sub-pattern. - PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _, _) | PatKind::Box(pat) | PatKind::Deref(pat) => { + PatKind::Binding(_, _, _, Some(pat)) + | PatKind::Ref(pat, _, _) + | PatKind::Box(pat) + | PatKind::Deref(pat) => { self.add_pat(cx, pat) }, PatKind::Tuple([sub_pat], pos) diff --git a/src/tools/clippy/clippy_lints/src/mem_replace.rs b/src/tools/clippy/clippy_lints/src/mem_replace.rs index ac3cbaec55f3..0f32f89666a0 100644 --- a/src/tools/clippy/clippy_lints/src/mem_replace.rs +++ b/src/tools/clippy/clippy_lints/src/mem_replace.rs @@ -1,13 +1,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{is_default_equivalent, is_expr_used_or_unified, peel_ref_operators, std_or_core}; +use clippy_utils::{ + as_some_expr, is_default_equivalent, is_expr_used_or_unified, is_none_expr, peel_ref_operators, std_or_core, +}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -128,7 +129,7 @@ [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_OPTION_WITH_SOME, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) -> bool { - if src.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) { + if is_none_expr(cx, src) { // Since this is a late pass (already type-checked), // and we already know that the second argument is an // `Option`, we do not need to check the first @@ -161,8 +162,7 @@ fn check_replace_option_with_some( expr_span: Span, msrv: Msrv, ) -> bool { - if let ExprKind::Call(src_func, [src_arg]) = src.kind - && src_func.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + if let Some(src_arg) = as_some_expr(cx, src) && msrv.meets(cx, msrvs::OPTION_REPLACE) { // We do not have to check for a `const` context here, because `core::mem::replace()` and diff --git a/src/tools/clippy/clippy_lints/src/methods/err_expect.rs b/src/tools/clippy/clippy_lints/src/methods/err_expect.rs index 6e9aebcf18ae..4353f6302c4b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/err_expect.rs +++ b/src/tools/clippy/clippy_lints/src/methods/err_expect.rs @@ -1,7 +1,6 @@ use super::ERR_EXPECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::MaybeDef; use clippy_utils::ty::has_debug_impl; use rustc_errors::Applicability; use rustc_lint::LateContext; @@ -17,12 +16,10 @@ pub(super) fn check( err_span: Span, msrv: Msrv, ) { - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - // Grabs the `Result` type - && let result_type = cx.typeck_results().expr_ty(recv) - // Tests if the T type in a `Result` is not None - && let Some(data_type) = get_data_type(cx, result_type) - // Tests if the T type in a `Result` implements debug + let result_ty = cx.typeck_results().expr_ty(recv); + // Grabs the `Result` type + if let Some(data_type) = get_data_type(cx, result_ty) + // Tests if the T type in a `Result` implements Debug && has_debug_impl(cx, data_type) && msrv.meets(cx, msrvs::EXPECT_ERR) { @@ -41,7 +38,7 @@ pub(super) fn check( /// Given a `Result` type, return its data (`T`). fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().next(), + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => args.types().next(), _ => None, } } diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/src/tools/clippy/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 8183c30f8c56..cdef98be14af 100644 --- a/src/tools/clippy/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/src/tools/clippy/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -1,13 +1,11 @@ use std::iter::once; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig}; -use clippy_utils::{get_expr_use_or_unification_node, std_or_core, sym}; +use clippy_utils::{as_some_expr, get_expr_use_or_unification_node, is_none_expr, std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::hir_id::HirId; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -68,15 +66,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), - ExprKind::Path(ref p) - if cx - .qpath_res(p, recv.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - None - }, - ExprKind::Call(f, [arg]) if f.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) => Some(arg), + _ if is_none_expr(cx, recv) => None, + _ if let Some(arg) = as_some_expr(cx, recv) => Some(arg), _ => return, }; let iter_type = match method_name { diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs index 1c26648e26eb..e3bcca64e923 100644 --- a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -81,7 +81,8 @@ pub(super) fn check<'tcx>( } match it.kind { - PatKind::Binding(BindingMode(_, Mutability::Mut), _, _, _) | PatKind::Ref(_, _, Mutability::Mut) => { + PatKind::Binding(BindingMode(_, Mutability::Mut), _, _, _) + | PatKind::Ref(_, _, Mutability::Mut) => { to_be_discarded = true; false }, diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 20dfce914838..c22b0a548e3d 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -138,7 +138,6 @@ mod unnecessary_result_map_or_else; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unused_enumerate_index; mod unwrap_expect_used; mod useless_asref; mod useless_nonzero_new_unchecked; @@ -1084,7 +1083,7 @@ /// /// ### Why is this bad? /// In versions of the compiler before Rust 1.82.0, this bypasses the specialized - /// implementation of`ToString` and instead goes through the more expensive string + /// implementation of `ToString` and instead goes through the more expensive string /// formatting facilities. /// /// ### Example @@ -5026,7 +5025,6 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { zst_offset::check(cx, expr, recv); }, (sym::all, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => { @@ -5056,7 +5054,6 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } }, (sym::any, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, false); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( @@ -5170,7 +5167,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { }, (sym::expect, [_]) => { match method_call(recv) { - Some((sym::ok, recv, [], _, _)) => ok_expect::check(cx, expr, recv), + Some((sym::ok, recv_inner, [], _, _)) => ok_expect::check(cx, expr, recv, recv_inner), Some((sym::err, recv, [], err_span, _)) => { err_expect::check(cx, expr, recv, span, err_span, self.msrv); }, @@ -5216,7 +5213,6 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } }, (sym::filter_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FilterMap); filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); @@ -5231,11 +5227,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { ); }, (sym::find_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap); }, (sym::flat_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); lines_filter_map_ok::check_filter_or_flat_map( @@ -5263,20 +5257,17 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); unnecessary_fold::check(cx, expr, init, acc, span); }, - (sym::for_each, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - match method_call(recv) { - Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ), - _ => {}, - } + (sym::for_each, [arg]) => match method_call(recv) { + Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + _ => {}, }, (sym::get, [arg]) => { get_first::check(cx, expr, recv, arg); @@ -5337,7 +5328,6 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { }, (name @ (sym::map | sym::map_err), [m_arg]) => { if name == sym::map { - unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, self.msrv); map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); manual_is_variant_and::check_map(cx, expr); diff --git a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs index 4f005103d23f..055fdcabdd21 100644 --- a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs +++ b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs @@ -38,11 +38,14 @@ pub(super) fn check<'tcx>( Node::Expr(parent) => { check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); + let sugg: String; + let mut app; + if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { - let mut app = Applicability::MachineApplicable; + app = Applicability::MachineApplicable; let collect_ty = cx.typeck_results().expr_ty(collect_expr); - let sugg: String = match name.ident.name { + sugg = match name.ident.name { sym::len => { if let Some(adt) = collect_ty.ty_adt_def() && matches!( @@ -78,17 +81,23 @@ pub(super) fn check<'tcx>( }, _ => return, }; - - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - call_span.with_hi(parent.span.hi()), - NEEDLESS_COLLECT_MSG, - "replace with", - sugg, - app, - ); + } else if let ExprKind::Index(_, index, _) = parent.kind { + app = Applicability::MaybeIncorrect; + let snip = snippet_with_applicability(cx, index.span, "_", &mut app); + sugg = format!("nth({snip}).unwrap()"); + } else { + return; } + + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + call_span.with_hi(parent.span.hi()), + NEEDLESS_COLLECT_MSG, + "replace with", + sugg, + app, + ); }, Node::LetStmt(l) => { if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind diff --git a/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs b/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs index c9c1f4865b81..5f1cae130dae 100644 --- a/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs +++ b/src/tools/clippy/clippy_lints/src/methods/ok_expect.rs @@ -1,28 +1,35 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::res::MaybeDef; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::has_debug_impl; +use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; use super::OK_EXPECT; /// lint use of `ok().expect()` for `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - // lint if the caller of `ok()` is a `Result` - && let result_type = cx.typeck_results().expr_ty(recv) - && let Some(error_type) = get_error_type(cx, result_type) +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, recv_inner: &hir::Expr<'_>) { + let result_ty = cx.typeck_results().expr_ty(recv_inner); + // lint if the caller of `ok()` is a `Result` + if let Some(error_type) = get_error_type(cx, result_ty) && has_debug_impl(cx, error_type) + && let Some(span) = recv.span.trim_start(recv_inner.span) { - span_lint_and_help( + span_lint_and_then( cx, OK_EXPECT, expr.span, "called `ok().expect()` on a `Result` value", - None, - "you can call `expect()` directly on the `Result`", + |diag| { + let span = cx.sess().source_map().span_extend_while_whitespace(span); + diag.span_suggestion_verbose( + span, + "call `expect()` directly on the `Result`", + String::new(), + Applicability::MachineApplicable, + ); + }, ); } } @@ -30,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr /// Given a `Result` type, return its error type (`E`). fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().nth(1), + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => args.types().nth(1), _ => None, } } diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs index 342ffea51d65..817388915f18 100644 --- a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs +++ b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_none_expr; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -48,7 +49,7 @@ pub(super) fn check<'tcx>( return; } - if !def_arg.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) { + if !is_none_expr(cx, def_arg) { // nothing to lint! return; } diff --git a/src/tools/clippy/clippy_lints/src/methods/result_map_or_else_none.rs b/src/tools/clippy/clippy_lints/src/methods/result_map_or_else_none.rs index e2946c22a46b..d5477b9be4c1 100644 --- a/src/tools/clippy/clippy_lints/src/methods/result_map_or_else_none.rs +++ b/src/tools/clippy/clippy_lints/src/methods/result_map_or_else_none.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::peel_blocks; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; +use clippy_utils::{is_none_expr, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -25,7 +25,7 @@ pub(super) fn check<'tcx>( && let hir::ExprKind::Closure(&hir::Closure { body, .. }) = def_arg.kind && let body = cx.tcx.hir_body(body) // And finally we check that we return a `None` in the "else case". - && peel_blocks(body.value).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + && is_none_expr(cx, peel_blocks(body.value)) { let msg = "called `map_or_else(|_| None, Some)` on a `Result` value"; let self_snippet = snippet(cx, recv.span, ".."); diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs index 7f729ac7ca94..72f1c42da2ee 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,10 +1,10 @@ use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; -use clippy_utils::sym; use clippy_utils::ty::{is_copy, option_arg_ty}; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{as_some_expr, sym}; use core::ops::ControlFlow; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -47,8 +47,7 @@ pub(super) fn check<'tcx>( let sugg = if !found_filtering { // Check if the closure is .filter_map(|x| Some(x)) if kind.is_filter_map() - && let hir::ExprKind::Call(expr, [arg]) = body.value.kind - && expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + && let Some(arg) = as_some_expr(cx, body.value) && let hir::ExprKind::Path(_) = arg.kind { span_lint( diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index 410e973f855b..da6f03931e24 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath}; -use clippy_utils::{last_path_segment, sym}; +use clippy_utils::{is_none_expr, last_path_segment, sym}; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; use rustc_lint::LateContext; @@ -23,6 +23,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) - } } +#[expect(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -38,23 +39,24 @@ pub(super) fn check( } let (constructor, call_args, ty) = if let hir::ExprKind::Call(call, call_args) = init.kind { - let Some((qpath, hir_id)) = call.opt_qpath() else { - return; - }; - - let args = last_path_segment(qpath).args.map(|args| args.args); - let res = cx.qpath_res(qpath, hir_id); - - if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::OptionSome) { - (sym::Some, call_args, get_ty_from_args(args, 0)) - } else if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultOk) { - (sym::Ok, call_args, get_ty_from_args(args, 0)) - } else if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultErr) { - (sym::Err, call_args, get_ty_from_args(args, 1)) + if let Some((qpath, hir_id)) = call.opt_qpath() + && let args = last_path_segment(qpath).args.map(|args| args.args) + && let Some(did) = cx.qpath_res(qpath, hir_id).ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { + (sym::Some, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_ok_variant() { + (sym::Ok, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_err_variant() { + (sym::Err, call_args, get_ty_from_args(args, 1)) + } else { + return; + } } else { return; } - } else if init.res(cx).ctor_parent(cx).is_lang_item(cx, hir::LangItem::OptionNone) { + } else if is_none_expr(cx, init) { let call_args: &[hir::Expr<'_>] = &[]; (sym::None, call_args, None) } else { diff --git a/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs b/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs deleted file mode 100644 index a7d9b2e0fab0..000000000000 --- a/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs +++ /dev/null @@ -1,138 +0,0 @@ -use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::{expr_or_init, pat_is_wild}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; -use rustc_lint::LateContext; -use rustc_span::{Span, sym}; - -use crate::loops::UNUSED_ENUMERATE_INDEX; - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - // If we call a method on a `std::iter::Enumerate` instance - if recv_ty.is_diag_item(cx, sym::Enumerate) - // If we are calling a method of the `Iterator` trait - && cx.ty_based_def(call_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. - span_lint_hir_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, - enumerate_span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) - { - Some(elem_ty.span) - } else { - None - } -} diff --git a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs index 35d06780bcb8..808adb7e71ce 100644 --- a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs @@ -3,10 +3,11 @@ use clippy_utils::comparisons::{Rel, normalize_comparison}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; +use clippy_utils::higher::{If, Range}; +use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace, root_macro_call}; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{eq_expr_value, hash_expr, higher}; +use clippy_utils::{eq_expr_value, hash_expr}; use rustc_ast::{BinOpKind, LitKind, RangeLimits}; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; @@ -15,7 +16,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -134,15 +135,15 @@ macro_rules! int_lit_pat { fn assert_len_expr<'hir>( cx: &LateContext<'_>, expr: &'hir Expr<'hir>, -) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> { - let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) +) -> Option<(LengthComparison, usize, &'hir Expr<'hir>, Symbol)> { + let ((cmp, asserted_len, slice_len), macro_call) = if let Some(If { cond, then, .. }) = If::hir(expr) && let ExprKind::Unary(UnOp::Not, condition) = &cond.kind && let ExprKind::Binary(bin_op, left, right) = &condition.kind // check if `then` block has a never type expression && let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind && cx.typeck_results().expr_ty(then_expr).is_never() { - len_comparison(bin_op.node, left, right)? + (len_comparison(bin_op.node, left, right)?, sym::assert_macro) } else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| { match cx.tcx.get_diagnostic_name(macro_call.def_id) { Some(sym::assert_eq_macro) => Some((macro_call, BinOpKind::Eq)), @@ -151,7 +152,12 @@ fn assert_len_expr<'hir>( } }) && let Some((left, right, _)) = find_assert_eq_args(cx, expr, macro_call.expn) { - len_comparison(bin_op, left, right)? + ( + len_comparison(bin_op, left, right)?, + root_macro_call(expr.span) + .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id)) + .unwrap_or(sym::assert_macro), + ) } else { return None; }; @@ -160,7 +166,7 @@ fn assert_len_expr<'hir>( && cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() && method.ident.name == sym::len { - Some((cmp, asserted_len, recv)) + Some((cmp, asserted_len, recv, macro_call)) } else { None } @@ -174,6 +180,7 @@ enum IndexEntry<'hir> { comparison: LengthComparison, assert_span: Span, slice: &'hir Expr<'hir>, + macro_call: Symbol, }, /// `assert!` with indexing /// @@ -187,6 +194,7 @@ enum IndexEntry<'hir> { slice: &'hir Expr<'hir>, indexes: Vec, comparison: LengthComparison, + macro_call: Symbol, }, /// Indexing without an `assert!` IndexWithoutAssert { @@ -225,9 +233,9 @@ fn upper_index_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { && let LitKind::Int(Pu128(index), _) = lit.node { Some(index as usize) - } else if let Some(higher::Range { + } else if let Some(Range { end: Some(end), limits, .. - }) = higher::Range::hir(cx, expr) + }) = Range::hir(cx, expr) && let ExprKind::Lit(lit) = &end.kind && let LitKind::Int(Pu128(index @ 1..), _) = lit.node { @@ -258,6 +266,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni comparison, assert_span, slice, + macro_call, } => { if slice.span.lo() > assert_span.lo() { *entry = IndexEntry::AssertWithIndex { @@ -268,6 +277,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni slice, indexes: vec![expr.span], comparison: *comparison, + macro_call: *macro_call, }; } }, @@ -303,7 +313,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni /// Checks if the expression is an `assert!` expression and adds it to `asserts` fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap>>) { - if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) { + if let Some((comparison, asserted_len, slice, macro_call)) = assert_len_expr(cx, expr) { let hash = hash_expr(cx, slice); let indexes = map.entry(hash).or_default(); @@ -326,6 +336,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un assert_span: expr.span.source_callsite(), comparison, asserted_len, + macro_call, }; } } else { @@ -334,6 +345,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un comparison, assert_span: expr.span.source_callsite(), slice, + macro_call, }); } } @@ -362,6 +374,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap comparison, assert_span, slice, + macro_call, } if indexes.len() > 1 && !is_first_highest => { // if we have found an `assert!`, let's also check that it's actually right // and if it covers the highest index and if not, suggest the correct length @@ -382,11 +395,23 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap snippet(cx, slice.span, "..") )), // `highest_index` here is rather a length, so we need to add 1 to it - LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => Some(format!( - "assert!({}.len() == {})", - snippet(cx, slice.span, ".."), - highest_index + 1 - )), + LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => match macro_call { + sym::assert_eq_macro => Some(format!( + "assert_eq!({}.len(), {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + sym::debug_assert_eq_macro => Some(format!( + "debug_assert_eq!({}.len(), {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + _ => Some(format!( + "assert!({}.len() == {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + }, _ => None, }; diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs index bccc72c2a516..c308f2a34585 100644 --- a/src/tools/clippy/clippy_lints/src/missing_inline.rs +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -4,6 +4,7 @@ use rustc_hir::{self as hir, Attribute, find_attr}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocContainer; +use rustc_session::config::CrateType; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -81,20 +82,20 @@ fn check_missing_inline_attrs( } } -fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool { - use rustc_session::config::CrateType; - - cx.tcx - .crate_types() - .iter() - .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro)) -} - declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); impl<'tcx> LateLintPass<'tcx> for MissingInline { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { - if it.span.in_external_macro(cx.sess().source_map()) || is_executable_or_proc_macro(cx) { + if it.span.in_external_macro(cx.sess().source_map()) { + return; + } + + if cx + .tcx + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) + { return; } @@ -149,7 +150,13 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if impl_item.span.in_external_macro(cx.sess().source_map()) || is_executable_or_proc_macro(cx) { + if impl_item.span.in_external_macro(cx.sess().source_map()) + || cx + .tcx + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) + { return; } diff --git a/src/tools/clippy/clippy_lints/src/module_style.rs b/src/tools/clippy/clippy_lints/src/module_style.rs index f132b90ac4f2..9096d6f1c7b3 100644 --- a/src/tools/clippy/clippy_lints/src/module_style.rs +++ b/src/tools/clippy/clippy_lints/src/module_style.rs @@ -4,7 +4,7 @@ use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; declare_clippy_lint! { @@ -150,7 +150,13 @@ fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile /// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test) /// for code-sharing between tests. fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { - if path.ends_with("mod.rs") && !path.starts_with("tests") { + if path.ends_with("mod.rs") + && !path + .components() + .filter_map(|c| if let Component::Normal(d) = c { Some(d) } else { None }) + .take_while(|&c| c != "src") + .any(|c| c == "tests") + { span_lint_and_then( cx, MOD_MODULE_FILES, diff --git a/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs b/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs index 5f7fde30f03f..691d9035d02c 100644 --- a/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs +++ b/src/tools/clippy/clippy_lints/src/needless_arbitrary_self_type.rs @@ -1,10 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Mutability, Param, PatKind, Path, TyKind}; +use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Param, PatKind, TyKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; use rustc_span::symbol::kw; declare_clippy_lint! { @@ -65,52 +64,6 @@ enum Mode { Value, } -fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mode: &Mode, mutbl: Mutability) { - if let [segment] = &path.segments[..] - && segment.ident.name == kw::SelfUpper - { - // In case we have a named lifetime, we check if the name comes from expansion. - // If it does, at this point we know the rest of the parameter was written by the user, - // so let them decide what the name of the lifetime should be. - // See #6089 for more details. - let mut applicability = Applicability::MachineApplicable; - let self_param = match (binding_mode, mutbl) { - (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Mut) => { - if lifetime.ident.span.from_expansion() { - applicability = Applicability::HasPlaceholders; - "&'_ mut self".to_string() - } else { - let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability); - format!("&{lt_name} mut self") - } - }, - (Mode::Ref(None), Mutability::Not) => "&self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Not) => { - if lifetime.ident.span.from_expansion() { - applicability = Applicability::HasPlaceholders; - "&'_ self".to_string() - } else { - let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability); - format!("&{lt_name} self") - } - }, - (Mode::Value, Mutability::Mut) => "mut self".to_string(), - (Mode::Value, Mutability::Not) => "self".to_string(), - }; - - span_lint_and_sugg( - cx, - NEEDLESS_ARBITRARY_SELF_TYPE, - span, - "the type of the `self` parameter does not need to be arbitrary", - "consider to change this parameter to", - self_param, - applicability, - ); - } -} - impl EarlyLintPass for NeedlessArbitrarySelfType { fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { // Bail out if the parameter it's not a receiver or was not written by the user @@ -118,20 +71,55 @@ fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { return; } - match &p.ty.kind { - TyKind::Path(None, path) => { - if let PatKind::Ident(BindingMode(ByRef::No, mutbl), _, _) = p.pat.kind { - check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Value, mutbl); - } + let (path, binding_mode, mutbl) = match &p.ty.kind { + TyKind::Path(None, path) if let PatKind::Ident(BindingMode(ByRef::No, mutbl), _, _) = p.pat.kind => { + (path, Mode::Value, mutbl) }, - TyKind::Ref(lifetime, mut_ty) => { + TyKind::Ref(lifetime, mut_ty) if let TyKind::Path(None, path) = &mut_ty.ty.kind - && let PatKind::Ident(BindingMode::NONE, _, _) = p.pat.kind - { - check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Ref(*lifetime), mut_ty.mutbl); - } + && let PatKind::Ident(BindingMode::NONE, _, _) = p.pat.kind => + { + (path, Mode::Ref(*lifetime), mut_ty.mutbl) }, - _ => {}, + _ => return, + }; + + let span = p.span.to(p.ty.span); + if let [segment] = &path.segments[..] + && segment.ident.name == kw::SelfUpper + { + span_lint_and_then( + cx, + NEEDLESS_ARBITRARY_SELF_TYPE, + span, + "the type of the `self` parameter does not need to be arbitrary", + |diag| { + let mut applicability = Applicability::MachineApplicable; + let add = match binding_mode { + Mode::Value => String::new(), + Mode::Ref(None) => mutbl.ref_prefix_str().to_string(), + Mode::Ref(Some(lifetime)) => { + // In case we have a named lifetime, we check if the name comes from expansion. + // If it does, at this point we know the rest of the parameter was written by the user, + // so let them decide what the name of the lifetime should be. + // See #6089 for more details. + let lt_name = if lifetime.ident.span.from_expansion() { + applicability = Applicability::HasPlaceholders; + "'_".into() + } else { + snippet_with_applicability(cx, lifetime.ident.span, "'_", &mut applicability) + }; + format!("&{lt_name} {mut_}", mut_ = mutbl.prefix_str()) + }, + }; + + let mut sugg = vec![(p.ty.span.with_lo(p.span.hi()), String::new())]; + if !add.is_empty() { + sugg.push((p.span.shrink_to_lo(), add)); + } + diag.multipart_suggestion_verbose("remove the type", sugg, applicability); + }, + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs index e11f775018ed..e66c088617cb 100644 --- a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs +++ b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs @@ -354,10 +354,7 @@ fn self_cmp_call<'tcx>( needs_fully_qualified: &mut bool, ) -> bool { match cmp_expr.kind { - ExprKind::Call(path, [_, _]) => path - .res(typeck) - .opt_def_id() - .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)), + ExprKind::Call(path, [_, _]) => path.res(typeck).is_diag_item(cx, sym::ord_cmp_method), ExprKind::MethodCall(_, recv, [_], ..) => { let ExprKind::Path(path) = recv.kind else { return false; 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 3c3e5fea4970..9b0008a29c6b 100644 --- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -706,7 +706,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze, // FIXME: we just assume mgca rhs's are freeze - _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| !self.is_init_expr_freeze( + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).is_some_and(|e| !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), cx.tcx.typeck(item.owner_id), @@ -749,7 +749,7 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_> !is_freeze }, // FIXME: we just assume mgca rhs's are freeze - _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| { + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).is_some_and(|e| { !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), @@ -806,7 +806,7 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze, // FIXME: we just assume mgca rhs's are freeze - _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| { + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).is_some_and(|e| { !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs index 0a6499e09583..91a069559f7b 100644 --- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,4 +1,5 @@ use super::ARITHMETIC_SIDE_EFFECTS; +use crate::clippy_utils::res::MaybeQPath as _; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; @@ -6,7 +7,7 @@ use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, UintTy}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; @@ -88,74 +89,16 @@ fn has_allowed_unary(&self, ty: Ty<'_>) -> bool { self.allowed_unary.contains(ty_string_elem) } - fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - if let ty::Adt(adt, substs) = ty.kind() - && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) - && let int_type = substs.type_at(0) - && matches!(int_type.kind(), ty::Uint(_)) - { - true - } else { - false - } - } - - /// Verifies built-in types that have specific allowed operations - fn has_specific_allowed_type_and_operation<'tcx>( - cx: &LateContext<'tcx>, - lhs_ty: Ty<'tcx>, - op: hir::BinOpKind, - rhs_ty: Ty<'tcx>, - ) -> bool { - let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); - let is_sat_or_wrap = |ty: Ty<'_>| ty.is_diag_item(cx, sym::Saturating) || ty.is_diag_item(cx, sym::Wrapping); - - // If the RHS is `NonZero`, then division or module by zero will never occur. - if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem { - return true; - } - - // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module. - if is_sat_or_wrap(lhs_ty) { - return !is_div_or_rem; - } - - false - } - - // For example, 8i32 or &i64::MAX. - fn is_integral(ty: Ty<'_>) -> bool { - ty.peel_refs().is_integral() - } - // Common entry-point to avoid code duplication. fn issue_lint<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if is_from_proc_macro(cx, expr) { return; } - let msg = "arithmetic operation that can potentially result in unexpected side-effects"; span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg); self.expr_span = Some(expr.span); } - /// Returns the numeric value of a literal integer originated from `expr`, if any. - /// - /// Literal integers can be originated from adhoc declarations like `1`, associated constants - /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, - fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { - let actual = peel_hir_expr_unary(expr).0; - if let hir::ExprKind::Lit(lit) = actual.kind - && let ast::LitKind::Int(n, _) = lit.node - { - return Some(n.get()); - } - if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { - return Some(n); - } - None - } - /// Methods like `add_assign` are send to their `BinOps` references. fn manage_sugar_methods<'tcx>( &mut self, @@ -213,59 +156,53 @@ fn manage_bin_ops<'tcx>( && let hir::ExprKind::MethodCall(method, receiver, [], _) = actual_lhs.kind && method.ident.name == sym::get && let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs() - && Self::is_non_zero_u(cx, receiver_ty) - && let Some(1) = Self::literal_integer(cx, actual_rhs) + && is_non_zero_u(cx, receiver_ty) + && literal_integer(cx, actual_rhs) == Some(1) { return; } let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); let rhs_ty = cx.typeck_results().expr_ty_adjusted(actual_rhs).peel_refs(); - if self.has_allowed_binary(lhs_ty, rhs_ty) { + if self.has_allowed_binary(lhs_ty, rhs_ty) + | has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) + | is_safe_due_to_smaller_source_type(cx, op, (actual_lhs, lhs_ty), actual_rhs) + | is_safe_due_to_smaller_source_type(cx, op, (actual_rhs, rhs_ty), actual_lhs) + { return; } - if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { - return; - } - - let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { + if is_integer(lhs_ty) && is_integer(rhs_ty) { if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { // At least for integers, shifts are already handled by the CTFE return; } - match ( - Self::literal_integer(cx, actual_lhs), - Self::literal_integer(cx, actual_rhs), - ) { - (None, None) => false, + match (literal_integer(cx, actual_lhs), literal_integer(cx, actual_rhs)) { (None, Some(n)) => match (&op, n) { // Division and module are always valid if applied to non-zero integers - (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true, + (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => return, // Adding or subtracting zeros is always a no-op (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) // Multiplication by 1 or 0 will never overflow | (hir::BinOpKind::Mul, 0 | 1) - => true, - _ => false, + => return, + _ => {}, }, - (Some(n), None) => match (&op, n) { - // Adding or subtracting zeros is always a no-op - (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) - // Multiplication by 1 or 0 will never overflow - | (hir::BinOpKind::Mul, 0 | 1) - => true, - _ => false, - }, - (Some(_), Some(_)) => { - matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) + (Some(n), None) + if matches!( + (&op, n), + // Adding or subtracting zeros is always a no-op + (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) + // Multiplication by 1 or 0 will never overflow + | (hir::BinOpKind::Mul, 0 | 1) + ) => + { + return; }, + (Some(_), Some(_)) if matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) => return, + _ => {}, } - } else { - false - }; - if !has_valid_op { - self.issue_lint(cx, expr); } + self.issue_lint(cx, expr); } /// There are some integer methods like `wrapping_div` that will panic depending on the @@ -285,7 +222,7 @@ fn manage_method_call<'tcx>( return; } let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver); - if !Self::is_integral(instance_ty) { + if !is_integer(instance_ty) { return; } self.manage_sugar_methods(cx, expr, receiver, ps, arg); @@ -293,7 +230,7 @@ fn manage_method_call<'tcx>( return; } let (actual_arg, _) = peel_hir_expr_refs(arg); - match Self::literal_integer(cx, actual_arg) { + match literal_integer(cx, actual_arg) { None | Some(0) => self.issue_lint(cx, arg), Some(_) => {}, } @@ -317,7 +254,7 @@ fn manage_unary_ops<'tcx>( return; } let actual_un_expr = peel_hir_expr_refs(un_expr).0; - if Self::literal_integer(cx, actual_un_expr).is_some() { + if literal_integer(cx, actual_un_expr).is_some() { return; } self.issue_lint(cx, expr); @@ -385,3 +322,120 @@ fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) } } } + +/// Detects a type-casting conversion and returns the type of the original expression. For +/// example, `let foo = u64::from(bar)`. +fn find_original_primitive_ty<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option> { + if let hir::ExprKind::Call(path, [arg]) = &expr.kind + && path.res(cx).opt_def_id().is_diag_item(&cx.tcx, sym::from_fn) + { + Some(cx.typeck_results().expr_ty(arg)) + } else { + None + } +} + +/// Verifies built-in types that have specific allowed operations +fn has_specific_allowed_type_and_operation<'tcx>( + cx: &LateContext<'tcx>, + lhs_ty: Ty<'tcx>, + op: hir::BinOpKind, + rhs_ty: Ty<'tcx>, +) -> bool { + let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); + let is_sat_or_wrap = |ty: Ty<'_>| matches!(ty.opt_diag_name(cx), Some(sym::Saturating | sym::Wrapping)); + + // If the RHS is `NonZero`, then division or module by zero will never occur. + if is_non_zero_u(cx, rhs_ty) && is_div_or_rem { + return true; + } + + // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module. + if is_sat_or_wrap(lhs_ty) { + return !is_div_or_rem; + } + + false +} + +// For example, `i8` or `u128` and possible associated references like `&&u16`. +fn is_integer(ty: Ty<'_>) -> bool { + ty.peel_refs().is_integral() +} + +fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, substs) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_type = substs.type_at(0) + && matches!(int_type.kind(), ty::Uint(_)) + { + true + } else { + false + } +} + +/// If one side is a literal it is possible to evaluate overflows as long as the other side has a +/// smaller type. `0` and `1` suffixes indicate different sides. +/// +/// For example, `1000u64 + u64::from(some_runtime_variable_of_type_u8)`. +fn is_safe_due_to_smaller_source_type( + cx: &LateContext<'_>, + op: hir::BinOpKind, + (expr0, ty0): (&hir::Expr<'_>, Ty<'_>), + expr1: &hir::Expr<'_>, +) -> bool { + let Some(num0) = literal_integer(cx, expr0) else { + return false; + }; + let Some(orig_ty1) = find_original_primitive_ty(cx, expr1) else { + return false; + }; + let Some(num1) = max_int_num(orig_ty1) else { + return false; + }; + let Some(rslt) = (match op { + hir::BinOpKind::Add => num0.checked_add(num1), + hir::BinOpKind::Mul => num0.checked_mul(num1), + _ => None, + }) else { + return false; + }; + match ty0.peel_refs().kind() { + ty::Uint(UintTy::U16) => u16::try_from(rslt).is_ok(), + ty::Uint(UintTy::U32) => u32::try_from(rslt).is_ok(), + ty::Uint(UintTy::U64) => u64::try_from(rslt).is_ok(), + ty::Uint(UintTy::U128) => true, + ty::Uint(UintTy::Usize) => usize::try_from(rslt).is_ok(), + _ => false, + } +} + +/// Returns the numeric value of a literal integer originated from `expr`, if any. +/// +/// Literal integers can be originated from adhoc declarations like `1`, associated constants +/// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, +fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { + let actual = peel_hir_expr_unary(expr).0; + if let hir::ExprKind::Lit(lit) = actual.kind + && let ast::LitKind::Int(n, _) = lit.node + { + return Some(n.get()); + } + if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { + return Some(n); + } + None +} + +fn max_int_num(ty: Ty<'_>) -> Option { + match ty.peel_refs().kind() { + ty::Uint(UintTy::U8) => Some(u8::MAX.into()), + ty::Uint(UintTy::U16) => Some(u16::MAX.into()), + ty::Uint(UintTy::U32) => Some(u32::MAX.into()), + ty::Uint(UintTy::U64) => Some(u64::MAX.into()), + ty::Uint(UintTy::U128) => Some(u128::MAX), + ty::Uint(UintTy::Usize) => usize::MAX.try_into().ok(), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs index c32c74a8fe60..85cf483fce90 100644 --- a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs +++ b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs @@ -6,16 +6,15 @@ use clippy_utils::ty::is_copy; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, eager_or_lazy, expr_requires_coercion, higher, is_else_clause, - is_in_const_context, peel_blocks, peel_hir_expr_while, + is_in_const_context, is_none_pattern, peel_blocks, peel_hir_expr_while, }; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; use rustc_hir::{ - Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatExpr, PatExprKind, PatKind, Path, - QPath, UnOp, + Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -313,11 +312,14 @@ fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { } fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> { - if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind { - let res = cx.qpath_res(qpath, pat.hir_id); - if res.ctor_parent(cx).is_lang_item(cx, OptionSome) { + if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind + && let res = cx.qpath_res(qpath, pat.hir_id) + && let Some(did) = res.ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { return Some((inner_pat, false)); - } else if res.ctor_parent(cx).is_lang_item(cx, ResultOk) { + } else if Some(did) == lang_items.result_ok_variant() { return Some((inner_pat, true)); } } @@ -376,14 +378,7 @@ fn try_convert_match<'tcx>( fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { match arm.pat.kind { - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) => cx - .qpath_res(qpath, *hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone), + _ if is_none_pattern(cx, arm.pat) => true, PatKind::TupleStruct(ref qpath, [first_pat], _) => { cx.qpath_res(qpath, arm.pat.hir_id) .ctor_parent(cx) diff --git a/src/tools/clippy/clippy_lints/src/ptr/cmp_null.rs b/src/tools/clippy/clippy_lints/src/ptr/cmp_null.rs new file mode 100644 index 000000000000..905b48e6d1d4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr/cmp_null.rs @@ -0,0 +1,49 @@ +use super::CMP_NULL; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_lint_allowed, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + l: &Expr<'_>, + r: &Expr<'_>, +) -> bool { + let non_null_path_snippet = match ( + is_lint_allowed(cx, CMP_NULL, expr.hir_id), + is_null_path(cx, l), + is_null_path(cx, r), + ) { + (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), + (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), + _ => return false, + }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + + span_lint_and_sugg( + cx, + CMP_NULL, + expr.span, + "comparing with null is better expressed by the `.is_null()` method", + "try", + format!("{invert}{non_null_path_snippet}.is_null()",), + Applicability::MachineApplicable, + ); + true +} + +fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(pathexp, []) = expr.kind { + matches!( + pathexp.basic_res().opt_diag_name(cx), + Some(sym::ptr_null | sym::ptr_null_mut) + ) + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/ptr/mod.rs b/src/tools/clippy/clippy_lints/src/ptr/mod.rs new file mode 100644 index 000000000000..6b2647e7b0a2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr/mod.rs @@ -0,0 +1,202 @@ +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, ImplItemKind, ItemKind, Node, TraitFn, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +mod cmp_null; +mod mut_from_ref; +mod ptr_arg; +mod ptr_eq; + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for function arguments of type `&String`, `&Vec`, + /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls + /// with the appropriate `.to_owned()`/`to_string()` calls. + /// + /// ### Why is this bad? + /// Requiring the argument to be of the specific type + /// makes the function less useful for no benefit; slices in the form of `&[T]` + /// or `&str` usually suffice and can be obtained from other types, too. + /// + /// ### Known problems + /// There may be `fn(&Vec)`-typed references pointing to your function. + /// If you have them, you will get a compiler error after applying this lint's + /// suggestions. You then have the choice to undo your changes or change the + /// type of the reference. + /// + /// Note that if the function is part of your public interface, there may be + /// other crates referencing it, of which you may not be aware. Carefully + /// deprecate the function before applying the lint suggestions in this case. + /// + /// ### Example + /// ```ignore + /// fn foo(&Vec) { .. } + /// ``` + /// + /// Use instead: + /// ```ignore + /// fn foo(&[u32]) { .. } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PTR_ARG, + style, + "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for equality comparisons with `ptr::null` + /// + /// ### Why is this bad? + /// It's easier and more readable to use the inherent + /// `.is_null()` + /// method instead + /// + /// ### Example + /// ```rust,ignore + /// use std::ptr; + /// + /// if x == ptr::null { + /// // .. + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// if x.is_null() { + /// // .. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_NULL, + style, + "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for functions that take immutable references and return + /// mutable ones. This will not trigger if no unsafe code exists as there + /// are multiple safe functions which will do this transformation + /// + /// To be on the conservative side, if there's at least one mutable + /// reference with the output lifetime, this lint will not trigger. + /// + /// ### Why is this bad? + /// Creating a mutable reference which can be repeatably derived from an + /// immutable reference is unsound as it allows creating multiple live + /// mutable references to the same object. + /// + /// This [error](https://github.com/rust-lang/rust/issues/39465) actually + /// lead to an interim Rust release 1.15.1. + /// + /// ### Known problems + /// This pattern is used by memory allocators to allow allocating multiple + /// objects while returning mutable references to each one. So long as + /// different mutable references are returned each time such a function may + /// be safe. + /// + /// ### Example + /// ```ignore + /// fn foo(&Foo) -> &mut Bar { .. } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MUT_FROM_REF, + correctness, + "fns that create mutable refs from immutable ref args" +} + +declare_clippy_lint! { + /// ### What it does + /// Use `std::ptr::eq` when applicable + /// + /// ### Why is this bad? + /// `ptr::eq` can be used to compare `&T` references + /// (which coerce to `*const T` implicitly) by their address rather than + /// comparing the values they point to. + /// + /// ### Example + /// ```no_run + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(a as *const _ as usize == b as *const _ as usize); + /// ``` + /// Use instead: + /// ```no_run + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(std::ptr::eq(a, b)); + /// ``` + #[clippy::version = "1.49.0"] + pub PTR_EQ, + style, + "use `std::ptr::eq` when comparing raw pointers" +} + +declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, PTR_EQ]); + +impl<'tcx> LateLintPass<'tcx> for Ptr { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(sig, trait_method) = &item.kind { + if matches!(trait_method, TraitFn::Provided(_)) { + // Handled by `check_body`. + return; + } + + mut_from_ref::check(cx, sig, None); + ptr_arg::check_trait_item(cx, item.owner_id, sig); + } + } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let mut parents = cx.tcx.hir_parent_iter(body.value.hir_id); + let (item_id, sig, is_trait_item) = match parents.next() { + Some((_, Node::Item(i))) => { + if let ItemKind::Fn { sig, .. } = &i.kind { + (i.owner_id, sig, false) + } else { + return; + } + }, + Some((_, Node::ImplItem(i))) => { + if !matches!(parents.next(), + Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) + ) { + return; + } + if let ImplItemKind::Fn(sig, _) = &i.kind { + (i.owner_id, sig, false) + } else { + return; + } + }, + Some((_, Node::TraitItem(i))) => { + if let TraitItemKind::Fn(sig, _) = &i.kind { + (i.owner_id, sig, true) + } else { + return; + } + }, + _ => return, + }; + + mut_from_ref::check(cx, sig, Some(body)); + ptr_arg::check_body(cx, body, item_id, sig, is_trait_item); + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(op, l, r) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + { + #[expect( + clippy::collapsible_if, + reason = "the outer `if`s check the HIR, the inner ones run lints" + )] + if !cmp_null::check(cx, expr, op.node, l, r) { + ptr_eq::check(cx, op.node, l, r, expr.span); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/ptr/mut_from_ref.rs b/src/tools/clippy/clippy_lints/src/ptr/mut_from_ref.rs new file mode 100644 index 000000000000..30d708f436b4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr/mut_from_ref.rs @@ -0,0 +1,75 @@ +use super::MUT_FROM_REF; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::visitors::contains_unsafe_block; +use rustc_errors::MultiSpan; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{self as hir, Body, FnRetTy, FnSig, GenericArg, Lifetime, Mutability, TyKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { + let FnRetTy::Return(ty) = sig.decl.output else { return }; + for (out, mutability, out_span) in get_lifetimes(ty) { + if mutability != Some(Mutability::Mut) { + continue; + } + let out_region = cx.tcx.named_bound_var(out.hir_id); + // `None` if one of the types contains `&'a mut T` or `T<'a>`. + // Else, contains all the locations of `&'a T` types. + let args_immut_refs: Option> = sig + .decl + .inputs + .iter() + .flat_map(get_lifetimes) + .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) + .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) + .collect(); + if let Some(args_immut_refs) = args_immut_refs + && !args_immut_refs.is_empty() + && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) + { + span_lint_and_then( + cx, + MUT_FROM_REF, + out_span, + "mutable borrow from immutable input(s)", + |diag| { + let ms = MultiSpan::from_spans(args_immut_refs); + diag.span_note(ms, "immutable borrow here"); + }, + ); + } + } +} + +struct LifetimeVisitor<'tcx> { + result: Vec<(&'tcx Lifetime, Option, Span)>, +} + +impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { + if let TyKind::Ref(lt, ref m) = ty.kind { + self.result.push((lt, Some(m.mutbl), ty.span)); + } + hir::intravisit::walk_ty(self, ty); + } + + fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { + if let GenericArg::Lifetime(lt) = generic_arg { + self.result.push((lt, None, generic_arg.span())); + } + hir::intravisit::walk_generic_arg(self, generic_arg); + } +} + +/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. +/// +/// The second field of the vector's elements indicate if the lifetime is attached to a +/// shared reference, a mutable reference, or neither. +fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { + use hir::intravisit::VisitorExt as _; + + let mut visitor = LifetimeVisitor { result: Vec::new() }; + visitor.visit_ty_unambig(ty); + visitor.result +} diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr/ptr_arg.rs similarity index 50% rename from src/tools/clippy/clippy_lints/src/ptr.rs rename to src/tools/clippy/clippy_lints/src/ptr/ptr_arg.rs index 8446b6fbbea5..fd9230f00a8b 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr/ptr_arg.rs @@ -1,24 +1,22 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; -use clippy_utils::res::{MaybeDef, MaybeResPath}; +use super::PTR_ARG; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::MaybeResPath; 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, std_or_core, sym}; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, sym}; use hir::LifetimeKind; use rustc_abi::ExternAbi; -use rustc_errors::{Applicability, MultiSpan}; +use rustc_errors::Applicability; use rustc_hir::hir_id::{HirId, HirIdMap}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{ - self as hir, AnonConst, BinOpKind, BindingMode, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg, ImplItemKind, - ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind, TyKind, + self as hir, AnonConst, BindingMode, Body, Expr, ExprKind, FnSig, GenericArg, Lifetime, Mutability, Node, OwnerId, + Param, PatKind, QPath, TyKind, }; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{Obligation, ObligationCause}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; -use rustc_session::declare_lint_pass; use rustc_span::Span; use rustc_span::symbol::Symbol; use rustc_trait_selection::infer::InferCtxtExt as _; @@ -27,260 +25,65 @@ use crate::vec::is_allowed_vec_method; -declare_clippy_lint! { - /// ### What it does - /// This lint checks for function arguments of type `&String`, `&Vec`, - /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls - /// with the appropriate `.to_owned()`/`to_string()` calls. - /// - /// ### Why is this bad? - /// Requiring the argument to be of the specific type - /// makes the function less useful for no benefit; slices in the form of `&[T]` - /// or `&str` usually suffice and can be obtained from other types, too. - /// - /// ### Known problems - /// There may be `fn(&Vec)`-typed references pointing to your function. - /// If you have them, you will get a compiler error after applying this lint's - /// suggestions. You then have the choice to undo your changes or change the - /// type of the reference. - /// - /// Note that if the function is part of your public interface, there may be - /// other crates referencing it, of which you may not be aware. Carefully - /// deprecate the function before applying the lint suggestions in this case. - /// - /// ### Example - /// ```ignore - /// fn foo(&Vec) { .. } - /// ``` - /// - /// Use instead: - /// ```ignore - /// fn foo(&[u32]) { .. } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PTR_ARG, - style, - "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for equality comparisons with `ptr::null` - /// - /// ### Why is this bad? - /// It's easier and more readable to use the inherent - /// `.is_null()` - /// method instead - /// - /// ### Example - /// ```rust,ignore - /// use std::ptr; - /// - /// if x == ptr::null { - /// // .. - /// } - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// if x.is_null() { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_NULL, - style, - "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for functions that take immutable references and return - /// mutable ones. This will not trigger if no unsafe code exists as there - /// are multiple safe functions which will do this transformation - /// - /// To be on the conservative side, if there's at least one mutable - /// reference with the output lifetime, this lint will not trigger. - /// - /// ### Why is this bad? - /// Creating a mutable reference which can be repeatably derived from an - /// immutable reference is unsound as it allows creating multiple live - /// mutable references to the same object. - /// - /// This [error](https://github.com/rust-lang/rust/issues/39465) actually - /// lead to an interim Rust release 1.15.1. - /// - /// ### Known problems - /// This pattern is used by memory allocators to allow allocating multiple - /// objects while returning mutable references to each one. So long as - /// different mutable references are returned each time such a function may - /// be safe. - /// - /// ### Example - /// ```ignore - /// fn foo(&Foo) -> &mut Bar { .. } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MUT_FROM_REF, - correctness, - "fns that create mutable refs from immutable ref args" -} - -declare_clippy_lint! { - /// ### What it does - /// Use `std::ptr::eq` when applicable - /// - /// ### Why is this bad? - /// `ptr::eq` can be used to compare `&T` references - /// (which coerce to `*const T` implicitly) by their address rather than - /// comparing the values they point to. - /// - /// ### Example - /// ```no_run - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(a as *const _ as usize == b as *const _ as usize); - /// ``` - /// Use instead: - /// ```no_run - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(std::ptr::eq(a, b)); - /// ``` - #[clippy::version = "1.49.0"] - pub PTR_EQ, - style, - "use `std::ptr::eq` when comparing raw pointers" -} - -declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, PTR_EQ]); - -impl<'tcx> LateLintPass<'tcx> for Ptr { - fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(sig, trait_method) = &item.kind { - if matches!(trait_method, TraitFn::Provided(_)) { - // Handled by check body. - return; - } - - check_mut_from_ref(cx, sig, None); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - for arg in check_fn_args( - cx, - cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(), - sig.decl.inputs, - &[], - ) - .filter(|arg| arg.mutability() == Mutability::Not) - { - span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { - diag.span_suggestion( - arg.span, - "change this to", - format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), - Applicability::Unspecified, - ); - }); - } - } +pub(super) fn check_body<'tcx>( + cx: &LateContext<'tcx>, + body: &Body<'tcx>, + item_id: OwnerId, + sig: &FnSig<'tcx>, + is_trait_item: bool, +) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; } - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { - let mut parents = cx.tcx.hir_parent_iter(body.value.hir_id); - let (item_id, sig, is_trait_item) = match parents.next() { - Some((_, Node::Item(i))) => { - if let ItemKind::Fn { sig, .. } = &i.kind { - (i.owner_id, sig, false) - } else { - return; - } - }, - Some((_, Node::ImplItem(i))) => { - if !matches!(parents.next(), - Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) - ) { - return; - } - if let ImplItemKind::Fn(sig, _) = &i.kind { - (i.owner_id, sig, false) - } else { - return; - } - }, - Some((_, Node::TraitItem(i))) => { - if let TraitItemKind::Fn(sig, _) = &i.kind { - (i.owner_id, sig, true) - } else { - return; - } - }, - _ => return, - }; + let decl = sig.decl; + let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); + let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) + .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) + .collect(); + let results = check_ptr_arg_usage(cx, body, &lint_args); - check_mut_from_ref(cx, sig, Some(body)); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); - let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) - .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) - .collect(); - let results = check_ptr_arg_usage(cx, body, &lint_args); - - for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { - span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { - diag.multipart_suggestion( - "change this to", - iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) - .chain(result.replacements.iter().map(|r| { - ( - r.expr_span, - format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), - ) - })) - .collect(), - Applicability::Unspecified, - ); - }); - } - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(op, l, r) = expr.kind - && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) - { - let non_null_path_snippet = match ( - is_lint_allowed(cx, CMP_NULL, expr.hir_id), - is_null_path(cx, l), - is_null_path(cx, r), - ) { - (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), - (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), - _ => return check_ptr_eq(cx, expr, op.node, l, r), - }; - let invert = if op.node == BinOpKind::Eq { "" } else { "!" }; - - span_lint_and_sugg( - cx, - CMP_NULL, - expr.span, - "comparing with null is better expressed by the `.is_null()` method", - "try", - format!("{invert}{non_null_path_snippet}.is_null()",), - Applicability::MachineApplicable, + for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { + span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { + diag.multipart_suggestion( + "change this to", + iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) + .chain(result.replacements.iter().map(|r| { + ( + r.expr_span, + format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), + ) + })) + .collect(), + Applicability::Unspecified, ); - } + }); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item_id: OwnerId, sig: &FnSig<'tcx>) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + + for arg in check_fn_args( + cx, + cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(), + sig.decl.inputs, + &[], + ) + .filter(|arg| arg.mutability() == Mutability::Not) + { + span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { + diag.span_suggestion( + arg.span, + "change this to", + format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), + Applicability::Unspecified, + ); + }); } } @@ -393,10 +196,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( hir_tys: &'tcx [hir::Ty<'tcx>], params: &'tcx [Param<'tcx>], ) -> impl Iterator> + 'cx { - fn_sig - .inputs() - .iter() - .zip(hir_tys.iter()) + iter::zip(fn_sig.inputs(), hir_tys) .enumerate() .filter_map(move |(i, (ty, hir_ty))| { if let ty::Ref(_, ty, mutability) = *ty.kind() @@ -499,41 +299,6 @@ fn check_fn_args<'cx, 'tcx: 'cx>( }) } -fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { - let FnRetTy::Return(ty) = sig.decl.output else { return }; - for (out, mutability, out_span) in get_lifetimes(ty) { - if mutability != Some(Mutability::Mut) { - continue; - } - let out_region = cx.tcx.named_bound_var(out.hir_id); - // `None` if one of the types contains `&'a mut T` or `T<'a>`. - // Else, contains all the locations of `&'a T` types. - let args_immut_refs: Option> = sig - .decl - .inputs - .iter() - .flat_map(get_lifetimes) - .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) - .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) - .collect(); - if let Some(args_immut_refs) = args_immut_refs - && !args_immut_refs.is_empty() - && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) - { - span_lint_and_then( - cx, - MUT_FROM_REF, - out_span, - "mutable borrow from immutable input(s)", - |diag| { - let ms = MultiSpan::from_spans(args_immut_refs); - diag.span_note(ms, "immutable borrow here"); - }, - ); - } - } -} - #[expect(clippy::too_many_lines)] fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { struct V<'cx, 'tcx> { @@ -658,11 +423,11 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) { match param.pat.kind { PatKind::Binding(BindingMode::NONE, id, ident, None) if !is_lint_allowed(cx, PTR_ARG, param.hir_id) - // Let's not lint for the current parameter. The user may still intend to mutate - // (or, if not mutate, then perhaps call a method that's not otherwise available - // for) the referenced value behind the parameter with the underscore being only - // temporary. - && !ident.name.as_str().starts_with('_') => + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter with the underscore being only + // temporary. + && !ident.name.as_str().starts_with('_') => { Some((id, i)) }, @@ -708,123 +473,3 @@ fn matches_preds<'tcx>( .must_apply_modulo_regions(), }) } - -struct LifetimeVisitor<'tcx> { - result: Vec<(&'tcx Lifetime, Option, Span)>, -} - -impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { - fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { - if let TyKind::Ref(lt, ref m) = ty.kind { - self.result.push((lt, Some(m.mutbl), ty.span)); - } - hir::intravisit::walk_ty(self, ty); - } - - fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { - if let GenericArg::Lifetime(lt) = generic_arg { - self.result.push((lt, None, generic_arg.span())); - } - hir::intravisit::walk_generic_arg(self, generic_arg); - } -} - -/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. -/// -/// The second field of the vector's elements indicate if the lifetime is attached to a -/// shared reference, a mutable reference, or neither. -fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { - use hir::intravisit::VisitorExt as _; - - let mut visitor = LifetimeVisitor { result: Vec::new() }; - visitor.visit_ty_unambig(ty); - visitor.result -} - -fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::Call(pathexp, []) = expr.kind { - matches!( - pathexp.basic_res().opt_diag_name(cx), - Some(sym::ptr_null | sym::ptr_null_mut) - ) - } else { - false - } -} - -fn check_ptr_eq<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - op: BinOpKind, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, -) { - if expr.span.from_expansion() { - return; - } - - // Remove one level of usize conversion if any - let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { - (Some(lhs), Some(rhs)) => (lhs, rhs, true), - _ => (left, right, false), - }; - - // This lint concerns raw pointers - let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); - if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { - return; - } - - let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = - (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); - - if !(usize_peeled || left_casts_peeled || right_casts_peeled) { - return; - } - - let mut app = Applicability::MachineApplicable; - let left_snip = Sugg::hir_with_context(cx, left_var, expr.span.ctxt(), "_", &mut app); - let right_snip = Sugg::hir_with_context(cx, right_var, expr.span.ctxt(), "_", &mut app); - { - let Some(top_crate) = std_or_core(cx) else { return }; - let invert = if op == BinOpKind::Eq { "" } else { "!" }; - span_lint_and_sugg( - cx, - PTR_EQ, - expr.span, - format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), - "try", - format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), - app, - ); - } -} - -// If the given expression is a cast to a usize, return the lhs of the cast -// E.g., `foo as *const _ as usize` returns `foo as *const _`. -fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if !cast_expr.span.from_expansion() - && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize - && let ExprKind::Cast(expr, _) = cast_expr.kind - { - Some(expr) - } else { - None - } -} - -// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been -// peeled or not. -fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { - if !expr.span.from_expansion() - && let ExprKind::Cast(inner, _) = expr.kind - && let ty::RawPtr(target_ty, _) = expr_ty.kind() - && let inner_ty = cx.typeck_results().expr_ty(inner) - && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() - && target_ty == inner_target_ty - { - (peel_raw_casts(cx, inner, inner_ty).0, true) - } else { - (expr, false) - } -} diff --git a/src/tools/clippy/clippy_lints/src/ptr/ptr_eq.rs b/src/tools/clippy/clippy_lints/src/ptr/ptr_eq.rs new file mode 100644 index 000000000000..c982bb1ffbc5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr/ptr_eq.rs @@ -0,0 +1,87 @@ +use super::PTR_EQ; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::std_or_core; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + span: Span, +) { + if span.from_expansion() { + return; + } + + // Remove one level of usize conversion if any + let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { + (Some(lhs), Some(rhs)) => (lhs, rhs, true), + _ => (left, right, false), + }; + + // This lint concerns raw pointers + let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); + if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { + return; + } + + let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = + (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); + + if !(usize_peeled || left_casts_peeled || right_casts_peeled) { + return; + } + + let mut app = Applicability::MachineApplicable; + let ctxt = span.ctxt(); + let left_snip = Sugg::hir_with_context(cx, left_var, ctxt, "_", &mut app); + let right_snip = Sugg::hir_with_context(cx, right_var, ctxt, "_", &mut app); + { + let Some(top_crate) = std_or_core(cx) else { return }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + span_lint_and_sugg( + cx, + PTR_EQ, + span, + format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), + "try", + format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), + app, + ); + } +} + +// If the given expression is a cast to a usize, return the lhs of the cast +// E.g., `foo as *const _ as usize` returns `foo as *const _`. +fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if !cast_expr.span.from_expansion() + && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize + && let ExprKind::Cast(expr, _) = cast_expr.kind + { + Some(expr) + } else { + None + } +} + +// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been +// peeled or not. +fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { + if !expr.span.from_expansion() + && let ExprKind::Cast(inner, _) = expr.kind + && let ty::RawPtr(target_ty, _) = expr_ty.kind() + && let inner_ty = cx.typeck_results().expr_ty(inner) + && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() + && target_ty == inner_target_ty + { + (peel_raw_casts(cx, inner, inner_ty).0, true) + } else { + (expr, false) + } +} diff --git a/src/tools/clippy/clippy_lints/src/question_mark.rs b/src/tools/clippy/clippy_lints/src/question_mark.rs index 14675015c35e..59d31f782bc3 100644 --- a/src/tools/clippy/clippy_lints/src/question_mark.rs +++ b/src/tools/clippy/clippy_lints/src/question_mark.rs @@ -150,7 +150,7 @@ fn init_expr_can_use_question_mark(cx: &LateContext<'_>, init_expr: &Expr<'_>) - let init_expr_str = Sugg::hir_with_applicability(cx, init_expr, "..", &mut applicability).maybe_paren(); // Take care when binding is `ref` let sugg = if let PatKind::Binding( - BindingMode(ByRef::Yes(_,ref_mutability), binding_mutability), + BindingMode(ByRef::Yes(_, ref_mutability), binding_mutability), _hir_id, ident, subpattern, diff --git a/src/tools/clippy/clippy_lints/src/replace_box.rs b/src/tools/clippy/clippy_lints/src/replace_box.rs index 4bbd1803a78d..638f6dc1532b 100644 --- a/src/tools/clippy/clippy_lints/src/replace_box.rs +++ b/src/tools/clippy/clippy_lints/src/replace_box.rs @@ -3,11 +3,17 @@ use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::{is_default_equivalent_call, local_is_initialized}; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::smallvec::SmallVec; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, QPath}; +use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, LangItem, QPath}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::sym; +use rustc_middle::hir::place::ProjectionKind; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -33,17 +39,57 @@ perf, "assigning a newly created box to `Box` is inefficient" } -declare_lint_pass!(ReplaceBox => [REPLACE_BOX]); + +#[derive(Default)] +pub struct ReplaceBox { + consumed_locals: FxHashSet, + loaded_bodies: SmallVec<[BodyId; 2]>, +} + +impl ReplaceBox { + fn get_consumed_locals(&mut self, cx: &LateContext<'_>) -> &FxHashSet { + if let Some(body_id) = cx.enclosing_body + && !self.loaded_bodies.contains(&body_id) + { + self.loaded_bodies.push(body_id); + ExprUseVisitor::for_clippy( + cx, + cx.tcx.hir_body_owner_def_id(body_id), + MovedVariablesCtxt { + consumed_locals: &mut self.consumed_locals, + }, + ) + .consume_body(cx.tcx.hir_body(body_id)) + .into_ok(); + } + + &self.consumed_locals + } +} + +impl_lint_pass!(ReplaceBox => [REPLACE_BOX]); impl LateLintPass<'_> for ReplaceBox { + fn check_body_post(&mut self, _: &LateContext<'_>, body: &Body<'_>) { + if self.loaded_bodies.first().is_some_and(|&x| x == body.id()) { + self.consumed_locals.clear(); + self.loaded_bodies.clear(); + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = &expr.kind && !lhs.span.from_expansion() && !rhs.span.from_expansion() && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let Some(inner_ty) = lhs_ty.boxed_ty() // No diagnostic for late-initialized locals && lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local)) - && let Some(inner_ty) = lhs_ty.boxed_ty() + // No diagnostic if this is a local that has been moved, or the field + // of a local that has been moved, or several chained field accesses of a local + && local_base(lhs).is_none_or(|(base_id, _)| { + !self.get_consumed_locals(cx).contains(&base_id) + }) { if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) && implements_trait(cx, inner_ty, default_trait_id, &[]) @@ -109,3 +155,46 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option< None } } + +struct MovedVariablesCtxt<'a> { + consumed_locals: &'a mut FxHashSet, +} + +impl<'tcx> Delegate<'tcx> for MovedVariablesCtxt<'_> { + fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + if let PlaceBase::Local(id) = cmt.place.base + && let mut projections = cmt + .place + .projections + .iter() + .filter(|x| matches!(x.kind, ProjectionKind::Deref)) + // Either no deref or multiple derefs + && (projections.next().is_none() || projections.next().is_some()) + { + self.consumed_locals.insert(id); + } + } + + fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +/// A local place followed by optional fields +type IdFields = (HirId, Vec); + +/// If `expr` is a local variable with optional field accesses, return it. +fn local_base(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Path(qpath) => qpath.res_local_id().map(|id| (id, Vec::new())), + ExprKind::Field(expr, field) => local_base(expr).map(|(id, mut fields)| { + fields.push(field.name); + (id, fields) + }), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/returns/let_and_return.rs b/src/tools/clippy/clippy_lints/src/returns/let_and_return.rs index f54a26a77620..0a00981e15be 100644 --- a/src/tools/clippy/clippy_lints/src/returns/let_and_return.rs +++ b/src/tools/clippy/clippy_lints/src/returns/let_and_return.rs @@ -3,7 +3,7 @@ use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_cfg}; +use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_non_whitespace}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, PatKind, StmtKind}; @@ -27,7 +27,7 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) && !initexpr.span.in_external_macro(cx.sess().source_map()) && !retexpr.span.in_external_macro(cx.sess().source_map()) && !local.span.from_expansion() - && !span_contains_cfg(cx, stmt.span.between(retexpr.span)) + && !span_contains_non_whitespace(cx, stmt.span.between(retexpr.span), true) { span_lint_hir_and_then( cx, diff --git a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs index c4604fb1558d..fabb21f78b9e 100644 --- a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs +++ b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; -use clippy_utils::{expr_or_init, get_attr, peel_hir_expr_unary, sym}; +use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -167,7 +167,7 @@ fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { if let Some(adt) = ty.ty_adt_def() { - let mut iter = get_attr( + let mut iter = get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/src/tools/clippy/clippy_lints/src/single_range_in_vec_init.rs b/src/tools/clippy/clippy_lints/src/single_range_in_vec_init.rs index 412ca2fa4ed9..92d1b112198f 100644 --- a/src/tools/clippy/clippy_lints/src/single_range_in_vec_init.rs +++ b/src/tools/clippy/clippy_lints/src/single_range_in_vec_init.rs @@ -1,14 +1,15 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::ty::implements_trait; use clippy_utils::{is_no_std_crate, sym}; use rustc_ast::{LitIntType, LitKind, UintTy}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, StructTailExpr}; +use rustc_hir::{Expr, ExprKind, StructTailExpr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::DesugaringKind; use std::fmt::{self, Display, Formatter}; declare_clippy_lint! { @@ -86,19 +87,21 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { return; }; - let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind else { + let ExprKind::Struct(_, [start, end], StructTailExpr::None) = inner_expr.kind else { return; }; - if cx.tcx.qpath_is_lang_item(qpath, LangItem::Range) + if inner_expr.span.is_desugaring(DesugaringKind::RangeExpr) && let ty = cx.typeck_results().expr_ty(start.expr) && let Some(snippet) = span.get_source_text(cx) // `is_from_proc_macro` will skip any `vec![]`. Let's not! && snippet.starts_with(suggested_type.starts_with()) && snippet.ends_with(suggested_type.ends_with()) - && let Some(start_snippet) = start.span.get_source_text(cx) - && let Some(end_snippet) = end.span.get_source_text(cx) { + let mut applicability = Applicability::MachineApplicable; + let (start_snippet, _) = snippet_with_context(cx, start.expr.span, span.ctxt(), "..", &mut applicability); + let (end_snippet, _) = snippet_with_context(cx, end.expr.span, span.ctxt(), "..", &mut applicability); + let should_emit_every_value = if let Some(step_def_id) = cx.tcx.get_diagnostic_item(sym::range_step) && implements_trait(cx, ty, step_def_id, &[]) { @@ -129,7 +132,7 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { span, "if you wanted a `Vec` that contains the entire range, try", format!("({start_snippet}..{end_snippet}).collect::>()"), - Applicability::MaybeIncorrect, + applicability, ); } @@ -138,7 +141,7 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { inner_expr.span, format!("if you wanted {suggested_type} of len {end_snippet}, try"), format!("{start_snippet}; {end_snippet}"), - Applicability::MaybeIncorrect, + applicability, ); } }, diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs index 5fda388259a6..d643f7aea497 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs @@ -435,10 +435,11 @@ /// to infer a technically correct yet unexpected type. /// /// ### Example - /// ```no_run + /// ``` /// # unsafe { + /// let mut x: i32 = 0; /// // Avoid "naked" calls to `transmute()`! - /// let x: i32 = std::mem::transmute([1u16, 2u16]); + /// x = std::mem::transmute([1u16, 2u16]); /// /// // `first_answers` is intended to transmute a slice of bool to a slice of u8. /// // But the programmer forgot to index the first element of the outer slice, @@ -449,7 +450,7 @@ /// # } /// ``` /// Use instead: - /// ```no_run + /// ``` /// # unsafe { /// let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); /// diff --git a/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs index 46d9febb187f..43b38bb662dc 100644 --- a/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs +++ b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs @@ -1,115 +1,59 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::qpath_generic_tys; -use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, QPath, TyKind}; +use rustc_hir::{QPath, Ty, TyKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; +use std::borrow::Cow; use super::RC_BUFFER; -pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { - let app = Applicability::Unspecified; - let name = cx.tcx.get_diagnostic_name(def_id); - if name == Some(sym::Rc) { - if let Some(alternate) = match_buffer_type(cx, qpath) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc` when T is a buffer type", - |diag| { - diag.span_suggestion(hir_ty.span, "try", format!("Rc<{alternate}>"), app); - }, - ); - } else { - let Some(ty) = qpath_generic_tys(qpath).next() else { - return false; - }; - if !ty.basic_res().is_diag_item(cx, sym::Vec) { - return false; - } - let TyKind::Path(qpath) = &ty.kind else { return false }; - let inner_span = match qpath_generic_tys(qpath).next() { - Some(ty) => ty.span, - None => return false, - }; - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc` when T is a buffer type", - |diag| { - let mut applicability = app; - diag.span_suggestion( - hir_ty.span, - "try", - format!( - "Rc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - app, - ); - }, - ); - return true; - } - } else if name == Some(sym::Arc) { - if let Some(alternate) = match_buffer_type(cx, qpath) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc` when T is a buffer type", - |diag| { - diag.span_suggestion(hir_ty.span, "try", format!("Arc<{alternate}>"), app); - }, - ); - } else if let Some(ty) = qpath_generic_tys(qpath).next() { - if !ty.basic_res().is_diag_item(cx, sym::Vec) { - return false; - } - let TyKind::Path(qpath) = &ty.kind else { return false }; - let inner_span = match qpath_generic_tys(qpath).next() { - Some(ty) => ty.span, - None => return false, - }; - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc` when T is a buffer type", - |diag| { - let mut applicability = app; - diag.span_suggestion( - hir_ty.span, - "try", - format!( - "Arc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - app, - ); - }, - ); - return true; - } +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + let mut app = Applicability::Unspecified; + let rc = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::Rc) => "Rc", + Some(sym::Arc) => "Arc", + _ => return false, + }; + if let Some(ty) = qpath_generic_tys(qpath).next() + && let Some(alternate) = match_buffer_type(cx, ty, &mut app) + { + span_lint_and_then( + cx, + RC_BUFFER, + hir_ty.span, + format!("usage of `{rc}` when `T` is a buffer type"), + |diag| { + diag.span_suggestion_verbose(ty.span, "try", alternate, app); + }, + ); + true + } else { + false } - - false } -fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { - let ty = qpath_generic_tys(qpath).next()?; +fn match_buffer_type( + cx: &LateContext<'_>, + ty: &Ty<'_>, + applicability: &mut Applicability, +) -> Option> { let id = ty.basic_res().opt_def_id()?; let path = match cx.tcx.get_diagnostic_name(id) { - Some(sym::OsString) => "std::ffi::OsStr", - Some(sym::PathBuf) => "std::path::Path", - _ if Some(id) == cx.tcx.lang_items().string() => "str", + Some(sym::OsString) => "std::ffi::OsStr".into(), + Some(sym::PathBuf) => "std::path::Path".into(), + Some(sym::Vec) => { + let TyKind::Path(vec_qpath) = &ty.kind else { + return None; + }; + let vec_generic_ty = qpath_generic_tys(vec_qpath).next()?; + let snippet = snippet_with_applicability(cx, vec_generic_ty.span, "_", applicability); + format!("[{snippet}]").into() + }, + _ if Some(id) == cx.tcx.lang_items().string() => "str".into(), _ => return None, }; Some(path) 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 11d3f33331cb..9d27a66a9ab8 100644 --- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -215,7 +215,13 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &hir::Item<'tcx>) { } } -fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, (span, help_span): (Span, Span), is_doc: bool) { +#[expect(clippy::too_many_lines)] +fn check_has_safety_comment<'tcx>( + cx: &LateContext<'tcx>, + item: &hir::Item<'tcx>, + (span, help_span): (Span, Span), + is_doc: bool, +) { match &item.kind { ItemKind::Impl(Impl { of_trait: Some(of_trait), @@ -236,12 +242,14 @@ fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx> ItemKind::Impl(_) => {}, // const and static items only need a safety comment if their body is an unsafe block, lint otherwise &ItemKind::Const(.., ct_rhs) => { - if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, ct_rhs.hir_id()) { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, ct_rhs.hir_id()) { let expr = const_item_rhs_to_expr(cx.tcx, ct_rhs); - if let Some(expr) = expr && !matches!( - expr.kind, hir::ExprKind::Block(block, _) - if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) - ) { + if let Some(expr) = expr + && !matches!( + expr.kind, hir::ExprKind::Block(block, _) + if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + ) + { span_lint_and_then( cx, UNNECESSARY_SAFETY_COMMENT, @@ -256,8 +264,8 @@ fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx> ); } } - } - &ItemKind::Static(.., body) => { + }, + &ItemKind::Static(.., body) => { if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) { let body = cx.tcx.hir_body(body); if !matches!( diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs b/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs index af9f291f5deb..fba530d0dfca 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs @@ -35,18 +35,17 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - if expr.span.from_expansion() { - return; - } - if let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind + if !expr.span.from_expansion() + && let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind + && !map_arg.span.from_expansion() + && let hir::ExprKind::Path(fun) = map_arg.kind && let Some(sym::Option | sym::Result) = cx.typeck_results().expr_ty(recv).opt_diag_name(cx) { let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, [arg, ..]) = recv.kind && let hir::ExprKind::Path(constructor_path) = constructor.kind + && !constructor.span.from_expansion() + && !arg.span.from_expansion() { - if constructor.span.from_expansion() || arg.span.from_expansion() { - return; - } (constructor_path, arg) } else { return; @@ -67,29 +66,22 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tc _ => return, } - if let hir::ExprKind::Path(fun) = map_arg.kind { - if map_arg.span.from_expansion() { - return; - } - let mut applicability = Applicability::MachineApplicable; - let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut applicability); - let constructor_snippet = - snippet_with_applicability(cx, constructor_path.span(), "_", &mut applicability); - let constructor_arg_snippet = - snippet_with_applicability(cx, constructor_item.span, "_", &mut applicability); - span_lint_and_sugg( - cx, - UNNECESSARY_MAP_ON_CONSTRUCTOR, - expr.span, - format!( - "unnecessary {} on constructor {constructor_snippet}(_)", - path.ident.name - ), - "try", - format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"), - applicability, - ); - } + let mut app = Applicability::MachineApplicable; + let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut app); + let constructor_snippet = snippet_with_applicability(cx, constructor_path.span(), "_", &mut app); + let constructor_arg_snippet = snippet_with_applicability(cx, constructor_item.span, "_", &mut app); + span_lint_and_sugg( + cx, + UNNECESSARY_MAP_ON_CONSTRUCTOR, + expr.span, + format!( + "unnecessary `{}` on constructor `{constructor_snippet}(_)`", + path.ident.name + ), + "try", + format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"), + app, + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs index bbb1b831888f..975dd332ad06 100644 --- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs +++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs @@ -10,6 +10,7 @@ use rustc_ast::{self as ast, DUMMY_NODE_ID, Mutability, Pat, PatKind, Pinnedness}; use rustc_ast_pretty::pprust; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_data_structures::thinvec::ExtractIf; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::impl_lint_pass; @@ -98,7 +99,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { return; } - let mut pat = Box::new(pat.clone()); + let mut pat = pat.clone(); // Nix all the paren patterns everywhere so that they aren't in our way. remove_all_parens(&mut pat); @@ -120,7 +121,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { } /// Remove all `(p)` patterns in `pat`. -fn remove_all_parens(pat: &mut Box) { +fn remove_all_parens(pat: &mut Pat) { #[derive(Default)] struct Visitor { /// If is not in the outer most pattern. This is needed to avoid removing the outermost @@ -143,7 +144,7 @@ fn visit_pat(&mut self, pat: &mut Pat) { } /// Insert parens where necessary according to Rust's precedence rules for patterns. -fn insert_necessary_parens(pat: &mut Box) { +fn insert_necessary_parens(pat: &mut Pat) { struct Visitor; impl MutVisitor for Visitor { fn visit_pat(&mut self, pat: &mut Pat) { @@ -152,7 +153,8 @@ fn visit_pat(&mut self, pat: &mut Pat) { let target = match &mut pat.kind { // `i @ a | b`, `box a | b`, and `& mut? a | b`. Ident(.., Some(p)) | Box(p) | Ref(p, _, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, - Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, // `&(mut x)` + // `&(mut x)` + Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, _ => return, }; target.kind = Paren(Box::new(take_pat(target))); @@ -163,7 +165,7 @@ fn visit_pat(&mut self, pat: &mut Pat) { /// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. /// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. -fn unnest_or_patterns(pat: &mut Box) -> bool { +fn unnest_or_patterns(pat: &mut Pat) -> bool { struct Visitor { changed: bool, } @@ -385,15 +387,14 @@ fn take_pat(from: &mut Pat) -> Pat { /// in `tail_or` if there are any and return if there were. fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec) -> bool { fn extend(target: &mut Pat, mut tail_or: ThinVec) { - match target { - // On an existing or-pattern in the target, append to it. - Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), - // Otherwise convert the target to an or-pattern. - target => { - let mut init_or = thin_vec![take_pat(target)]; - init_or.append(&mut tail_or); - target.kind = Or(init_or); - }, + // On an existing or-pattern in the target, append to it, + // otherwise convert the target to an or-pattern. + if let Or(ps) = &mut target.kind { + ps.append(&mut tail_or); + } else { + let mut init_or = thin_vec![take_pat(target)]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); } } @@ -416,26 +417,14 @@ fn drain_matching( let mut tail_or = ThinVec::new(); let mut idx = 0; - // If `ThinVec` had the `drain_filter` method, this loop could be rewritten - // like so: - // - // for pat in alternatives.drain_filter(|p| { - // // Check if we should extract, but only if `idx >= start`. - // idx += 1; - // idx > start && predicate(&p.kind) - // }) { - // tail_or.push(extract(pat.into_inner().kind)); - // } - let mut i = 0; - while i < alternatives.len() { - idx += 1; + // FIXME: once `thin-vec` releases a new version, change this to `alternatives.extract_if()` + // See https://github.com/mozilla/thin-vec/issues/77 + for pat in ExtractIf::new(alternatives, |p| { // Check if we should extract, but only if `idx >= start`. - if idx > start && predicate(&alternatives[i].kind) { - let pat = alternatives.remove(i); - tail_or.push(extract(pat.kind)); - } else { - i += 1; - } + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.kind)); } tail_or diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 57f889818be3..03cbb0311c6c 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -1,5 +1,5 @@ use clippy_utils::res::MaybeQPath; -use clippy_utils::{get_attr, higher, sym}; +use clippy_utils::{get_builtin_attr, higher, sym}; use itertools::Itertools; use rustc_ast::LitIntType; use rustc_ast::ast::{LitFloatType, LitKind}; @@ -859,5 +859,5 @@ macro_rules! kind { fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::author).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::author).count() > 0 } diff --git a/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs b/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs index d6cf07fdaf3f..b490866f0a11 100644 --- a/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs +++ b/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs @@ -1,4 +1,4 @@ -use clippy_utils::{get_attr, sym}; +use clippy_utils::{get_builtin_attr, sym}; use hir::TraitItem; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -60,5 +60,5 @@ fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &hir::ImplItem<'_>) { fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::dump).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::dump).count() > 0 } diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs deleted file mode 100644 index c39e4a4cc956..000000000000 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ /dev/null @@ -1,734 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::macros::{FormatArgsStorage, MacroCall, format_arg_removal_span, root_macro_call_first_node}; -use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; -use clippy_utils::{is_in_test, sym}; -use rustc_ast::token::LitKind; -use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, - FormatPlaceholder, FormatTrait, -}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_session::impl_lint_pass; -use rustc_span::{BytePos, Span}; - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `println!("")` to - /// print a newline. - /// - /// ### Why is this bad? - /// You should use `println!()`, which is simpler. - /// - /// ### Example - /// ```no_run - /// println!(""); - /// ``` - /// - /// Use instead: - /// ```no_run - /// println!(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINTLN_EMPTY_STRING, - style, - "using `println!(\"\")` with an empty string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `print!()` with a format - /// string that ends in a newline. - /// - /// ### Why is this bad? - /// You should use `println!()` instead, which appends the - /// newline. - /// - /// ### Example - /// ```no_run - /// # let name = "World"; - /// print!("Hello {}!\n", name); - /// ``` - /// use println!() instead - /// ```no_run - /// # let name = "World"; - /// println!("Hello {}!", name); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_WITH_NEWLINE, - style, - "using `print!()` with a format string that ends in a single newline" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for printing on *stdout*. The purpose of this lint - /// is to catch debugging remnants. - /// - /// ### Why restrict this? - /// People often print on *stdout* while debugging an - /// application and might forget to remove those prints afterward. - /// - /// ### Known problems - /// Only catches `print!` and `println!` calls. - /// - /// ### Example - /// ```no_run - /// println!("Hello world!"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_STDOUT, - restriction, - "printing on stdout" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for printing on *stderr*. The purpose of this lint - /// is to catch debugging remnants. - /// - /// ### Why restrict this? - /// People often print on *stderr* while debugging an - /// application and might forget to remove those prints afterward. - /// - /// ### Known problems - /// Only catches `eprint!` and `eprintln!` calls. - /// - /// ### Example - /// ```no_run - /// eprintln!("Hello world!"); - /// ``` - #[clippy::version = "1.50.0"] - pub PRINT_STDERR, - restriction, - "printing on stderr" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Debug` formatting. The purpose of this - /// lint is to catch debugging remnants. - /// - /// ### Why restrict this? - /// The purpose of the `Debug` trait is to facilitate debugging Rust code, - /// and [no guarantees are made about its output][stability]. - /// It should not be used in user-facing output. - /// - /// ### Example - /// ```no_run - /// # let foo = "bar"; - /// println!("{:?}", foo); - /// ``` - /// - /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability - #[clippy::version = "pre 1.29.0"] - pub USE_DEBUG, - restriction, - "use of `Debug`-based formatting" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns about the use of literals as `print!`/`println!` args. - /// - /// ### Why is this bad? - /// Using literals as `println!` args is inefficient - /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary - /// (i.e., just put the literal in the format string) - /// - /// ### Example - /// ```no_run - /// println!("{}", "foo"); - /// ``` - /// use the literal without formatting: - /// ```no_run - /// println!("foo"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_LITERAL, - style, - "printing a literal with a format string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `writeln!(buf, "")` to - /// print a newline. - /// - /// ### Why is this bad? - /// You should use `writeln!(buf)`, which is simpler. - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, ""); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITELN_EMPTY_STRING, - style, - "using `writeln!(buf, \"\")` with an empty string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `write!()` with a format - /// string that - /// ends in a newline. - /// - /// ### Why is this bad? - /// You should use `writeln!()` instead, which appends the - /// newline. - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// # let name = "World"; - /// write!(buf, "Hello {}!\n", name); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// # let name = "World"; - /// writeln!(buf, "Hello {}!", name); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITE_WITH_NEWLINE, - style, - "using `write!()` with a format string that ends in a single newline" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns about the use of literals as `write!`/`writeln!` args. - /// - /// ### Why is this bad? - /// Using literals as `writeln!` args is inefficient - /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary - /// (i.e., just put the literal in the format string) - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, "{}", "foo"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, "foo"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITE_LITERAL, - style, - "writing a literal with a format string" -} - -pub struct Write { - format_args: FormatArgsStorage, - // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated - outermost_debug_impl: Option, - allow_print_in_tests: bool, -} - -impl Write { - pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { - Self { - format_args, - outermost_debug_impl: None, - allow_print_in_tests: conf.allow_print_in_tests, - } - } - - fn in_debug_impl(&self) -> bool { - self.outermost_debug_impl.is_some() - } -} - -impl_lint_pass!(Write => [ - PRINT_WITH_NEWLINE, - PRINTLN_EMPTY_STRING, - PRINT_STDOUT, - PRINT_STDERR, - USE_DEBUG, - PRINT_LITERAL, - WRITE_WITH_NEWLINE, - WRITELN_EMPTY_STRING, - WRITE_LITERAL, -]); - -impl<'tcx> LateLintPass<'tcx> for Write { - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - // Only check for `impl Debug`s if we're not already in one - if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { - self.outermost_debug_impl = Some(item.owner_id); - } - } - - fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { - // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl - if self.outermost_debug_impl == Some(item.owner_id) { - self.outermost_debug_impl = None; - } - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(macro_call) = root_macro_call_first_node(cx, expr) else { - return; - }; - let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { - return; - }; - let Some(name) = diag_name.as_str().strip_suffix("_macro") else { - return; - }; - - let is_build_script = cx - .sess() - .opts - .crate_name - .as_ref() - .is_some_and(|crate_name| crate_name == "build_script_build"); - - let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); - match diag_name { - sym::print_macro | sym::println_macro if !allowed_in_tests => { - if !is_build_script { - span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); - } - }, - sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { - span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); - }, - sym::write_macro | sym::writeln_macro => {}, - _ => return, - } - - if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { - // ignore `writeln!(w)` and `write!(v, some_macro!())` - if format_args.span.from_expansion() { - return; - } - - match diag_name { - sym::print_macro | sym::eprint_macro | sym::write_macro => { - check_newline(cx, format_args, ¯o_call, name); - }, - sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { - check_empty_string(cx, format_args, ¯o_call, name); - }, - _ => {}, - } - - check_literal(cx, format_args, name); - - if !self.in_debug_impl() { - for piece in &format_args.template { - if let &FormatArgsPiece::Placeholder(FormatPlaceholder { - span: Some(span), - format_trait: FormatTrait::Debug, - .. - }) = piece - { - span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); - } - } - } - } - } -} - -fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { - if let ItemKind::Impl(Impl { - of_trait: Some(of_trait), - .. - }) = &item.kind - && let Some(trait_id) = of_trait.trait_ref.trait_def_id() - { - cx.tcx.is_diagnostic_item(sym::Debug, trait_id) - } else { - false - } -} - -fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { - return; - }; - - let count_vertical_whitespace = || { - format_args - .template - .iter() - .filter_map(|piece| match piece { - FormatArgsPiece::Literal(literal) => Some(literal), - FormatArgsPiece::Placeholder(_) => None, - }) - .flat_map(|literal| literal.as_str().chars()) - .filter(|ch| matches!(ch, '\r' | '\n')) - .count() - }; - - if last.as_str().ends_with('\n') - // ignore format strings with other internal vertical whitespace - && count_vertical_whitespace() == 1 - { - let mut format_string_span = format_args.span; - - let lint = if name == "write" { - format_string_span = expand_past_previous_comma(cx, format_string_span); - - WRITE_WITH_NEWLINE - } else { - PRINT_WITH_NEWLINE - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("using `{name}!()` with a format string that ends in a single newline"), - |diag| { - let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); - let Some(format_snippet) = format_string_span.get_source_text(cx) else { - return; - }; - - if format_args.template.len() == 1 && last == sym::LF { - // print!("\n"), write!(f, "\n") - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], - Applicability::MachineApplicable, - ); - } else if format_snippet.ends_with("\\n\"") { - // print!("...\n"), write!(f, "...\n") - - let hi = format_string_span.hi(); - let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (newline_span, String::new())], - Applicability::MachineApplicable, - ); - } - }, - ); - } -} - -fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { - let mut span = format_args.span; - - let lint = if name == "writeln" { - span = expand_past_previous_comma(cx, span); - - WRITELN_EMPTY_STRING - } else { - PRINTLN_EMPTY_STRING - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("empty string literal in `{name}!`"), - |diag| { - diag.span_suggestion( - span, - "remove the empty string", - String::new(), - Applicability::MachineApplicable, - ); - }, - ); - } -} - -fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { - let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); - - let lint_name = if name.starts_with("write") { - WRITE_LITERAL - } else { - PRINT_LITERAL - }; - - let mut counts = vec![0u32; format_args.arguments.all_args().len()]; - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece { - counts[arg_index(&placeholder.argument)] += 1; - } - } - - let mut suggestion: Vec<(Span, String)> = vec![]; - // holds index of replaced positional arguments; used to decrement the index of the remaining - // positional arguments. - let mut replaced_position: Vec = vec![]; - let mut sug_span: Option = None; - - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument, - span: Some(placeholder_span), - format_trait: FormatTrait::Display, - format_options, - }) = piece - && *format_options == FormatOptions::default() - && let index = arg_index(argument) - && counts[index] == 1 - && let Some(arg) = format_args.arguments.by_index(index) - && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind - && !arg.expr.span.from_expansion() - && let Some(value_string) = arg.expr.span.get_source_text(cx) - { - let (replacement, replace_raw) = match lit.kind { - LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { - Some(extracted) => extracted, - None => return, - }, - LitKind::Char => ( - match lit.symbol { - sym::DOUBLE_QUOTE => "\\\"", - sym::BACKSLASH_SINGLE_QUOTE => "'", - _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { - Some(stripped) => stripped, - None => return, - }, - } - .to_string(), - false, - ), - LitKind::Bool => (lit.symbol.to_string(), false), - _ => continue, - }; - - let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { - continue; - }; - let format_string_is_raw = format_string_snippet.starts_with('r'); - - let replacement = match (format_string_is_raw, replace_raw) { - (false, false) => Some(replacement), - (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), - (true, false) => match conservative_unescape(&replacement) { - Ok(unescaped) => Some(unescaped), - Err(UnescapeErr::Lint) => None, - Err(UnescapeErr::Ignore) => continue, - }, - (true, true) => { - if replacement.contains(['#', '"']) { - None - } else { - Some(replacement) - } - }, - }; - - sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - - if let Some((_, index)) = format_arg_piece_span(piece) { - replaced_position.push(index); - } - - if let Some(replacement) = replacement - // `format!("{}", "a")`, `format!("{named}", named = "b") - // ~~~~~ ~~~~~~~~~~~~~ - && let Some(removal_span) = format_arg_removal_span(format_args, index) - { - let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); - suggestion.push((*placeholder_span, replacement)); - suggestion.push((removal_span, String::new())); - } - } - } - - // Decrement the index of the remaining by the number of replaced positional arguments - if !suggestion.is_empty() { - for piece in &format_args.template { - relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); - } - } - - if let Some(span) = sug_span { - span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { - if !suggestion.is_empty() { - diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); - } - }); - } -} - -/// Extract Span and its index from the given `piece` -fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { - match piece { - FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: FormatArgPosition { index: Ok(index), .. }, - span: Some(span), - .. - }) => Some((*span, *index)), - _ => None, - } -} - -/// Relocalizes the indexes of positional arguments in the format string -fn relocalize_format_args_indexes( - piece: &FormatArgsPiece, - suggestion: &mut Vec<(Span, String)>, - replaced_position: &[usize], -) { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: - FormatArgPosition { - index: Ok(index), - // Only consider positional arguments - kind: FormatArgPositionKind::Number, - span: Some(span), - }, - format_options, - .. - }) = piece - { - if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { - // If the span is already in the suggestion, we don't need to process it again - return; - } - - // lambda to get the decremented index based on the replaced positions - let decremented_index = |index: usize| -> usize { - let decrement = replaced_position.iter().filter(|&&i| i < index).count(); - index - decrement - }; - - suggestion.push((*span, decremented_index(*index).to_string())); - - // If there are format options, we need to handle them as well - if *format_options != FormatOptions::default() { - // lambda to process width and precision format counts and add them to the suggestion - let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { - if let Some(FormatCount::Argument(FormatArgPosition { - index: Ok(format_arg_index), - kind: FormatArgPositionKind::Number, - span: Some(format_arg_span), - })) = count - { - suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); - } - }; - - process_format_count(&format_options.width, &|index: usize| format!("{index}$")); - process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); - } - } -} - -/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw -/// -/// `r#"a"#` -> (`a`, true) -/// -/// `"b"` -> (`b`, false) -fn extract_str_literal(literal: &str) -> Option<(String, bool)> { - let (literal, raw) = match literal.strip_prefix('r') { - Some(stripped) => (stripped.trim_matches('#'), true), - None => (literal, false), - }; - - Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) -} - -enum UnescapeErr { - /// Should still be linted, can be manually resolved by author, e.g. - /// - /// ```ignore - /// print!(r"{}", '"'); - /// ``` - Lint, - /// Should not be linted, e.g. - /// - /// ```ignore - /// print!(r"{}", '\r'); - /// ``` - Ignore, -} - -/// Unescape a normal string into a raw string -fn conservative_unescape(literal: &str) -> Result { - let mut unescaped = String::with_capacity(literal.len()); - let mut chars = literal.chars(); - let mut err = false; - - while let Some(ch) = chars.next() { - match ch { - '#' => err = true, - '\\' => match chars.next() { - Some('\\') => unescaped.push('\\'), - Some('"') => err = true, - _ => return Err(UnescapeErr::Ignore), - }, - _ => unescaped.push(ch), - } - } - - if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } -} - -/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in -/// `\u{xxxx}` are left unmodified -#[expect(clippy::match_same_arms)] -fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { - #[derive(Clone, Copy)] - enum State { - Normal, - Backslash, - UnicodeEscape, - } - - let mut escaped = String::with_capacity(literal.len()); - let mut state = State::Normal; - - for ch in literal.chars() { - state = match (ch, state) { - // Escape braces outside of unicode escapes by doubling them up - ('{' | '}', State::Normal) => { - escaped.push(ch); - State::Normal - }, - // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: - // - // \u{aaaa} \\ \x01 - // ^ ^ ^ - ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, - // \u{aaaa} - // ^ - ('u', State::Backslash) => State::UnicodeEscape, - // \xAA \\ - // ^ ^ - (_, State::Backslash) => State::Normal, - // \u{aaaa} - // ^ - ('}', State::UnicodeEscape) => State::Normal, - _ => state, - }; - - escaped.push(ch); - } - - escaped -} diff --git a/src/tools/clippy/clippy_lints/src/write/empty_string.rs b/src/tools/clippy/clippy_lints/src/write/empty_string.rs new file mode 100644 index 000000000000..e7eb99eb34ec --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write/empty_string.rs @@ -0,0 +1,38 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::expand_past_previous_comma; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; + +use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { + let mut span = format_args.span; + + let lint = if name == "writeln" { + span = expand_past_previous_comma(cx, span); + + WRITELN_EMPTY_STRING + } else { + PRINTLN_EMPTY_STRING + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("empty string literal in `{name}!`"), + |diag| { + diag.span_suggestion( + span, + "remove the empty string", + String::new(), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/write/literal.rs b/src/tools/clippy/clippy_lints/src/write/literal.rs new file mode 100644 index 000000000000..699ac7ea7a5c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write/literal.rs @@ -0,0 +1,285 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::format_arg_removal_span; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::sym; +use rustc_ast::token::LitKind; +use rustc_ast::{ + FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, +}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::{PRINT_LITERAL, WRITE_LITERAL}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { + let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); + + let lint_name = if name.starts_with("write") { + WRITE_LITERAL + } else { + PRINT_LITERAL + }; + + let mut counts = vec![0u32; format_args.arguments.all_args().len()]; + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece { + counts[arg_index(&placeholder.argument)] += 1; + } + } + + let mut suggestion: Vec<(Span, String)> = vec![]; + // holds index of replaced positional arguments; used to decrement the index of the remaining + // positional arguments. + let mut replaced_position: Vec = vec![]; + let mut sug_span: Option = None; + + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span: Some(placeholder_span), + format_trait: FormatTrait::Display, + format_options, + }) = piece + && *format_options == FormatOptions::default() + && let index = arg_index(argument) + && counts[index] == 1 + && let Some(arg) = format_args.arguments.by_index(index) + && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind + && !arg.expr.span.from_expansion() + && let Some(value_string) = arg.expr.span.get_source_text(cx) + { + let (replacement, replace_raw) = match lit.kind { + LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { + Some(extracted) => extracted, + None => return, + }, + LitKind::Char => ( + match lit.symbol { + sym::DOUBLE_QUOTE => "\\\"", + sym::BACKSLASH_SINGLE_QUOTE => "'", + _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + Some(stripped) => stripped, + None => return, + }, + } + .to_string(), + false, + ), + LitKind::Bool => (lit.symbol.to_string(), false), + _ => continue, + }; + + let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { + continue; + }; + let format_string_is_raw = format_string_snippet.starts_with('r'); + + let replacement = match (format_string_is_raw, replace_raw) { + (false, false) => Some(replacement), + (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), + (true, false) => match conservative_unescape(&replacement) { + Ok(unescaped) => Some(unescaped), + Err(UnescapeErr::Lint) => None, + Err(UnescapeErr::Ignore) => continue, + }, + (true, true) => { + if replacement.contains(['#', '"']) { + None + } else { + Some(replacement) + } + }, + }; + + sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); + + if let Some((_, index)) = format_arg_piece_span(piece) { + replaced_position.push(index); + } + + if let Some(replacement) = replacement + // `format!("{}", "a")`, `format!("{named}", named = "b") + // ~~~~~ ~~~~~~~~~~~~~ + && let Some(removal_span) = format_arg_removal_span(format_args, index) + { + let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); + suggestion.push((*placeholder_span, replacement)); + suggestion.push((removal_span, String::new())); + } + } + } + + // Decrement the index of the remaining by the number of replaced positional arguments + if !suggestion.is_empty() { + for piece in &format_args.template { + relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); + } + } + + if let Some(span) = sug_span { + span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { + if !suggestion.is_empty() { + diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); + } + }); + } +} + +/// Extract Span and its index from the given `piece` +fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { + match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { index: Ok(index), .. }, + span: Some(span), + .. + }) => Some((*span, *index)), + _ => None, + } +} + +/// Relocalizes the indexes of positional arguments in the format string +fn relocalize_format_args_indexes( + piece: &FormatArgsPiece, + suggestion: &mut Vec<(Span, String)>, + replaced_position: &[usize], +) { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: + FormatArgPosition { + index: Ok(index), + // Only consider positional arguments + kind: FormatArgPositionKind::Number, + span: Some(span), + }, + format_options, + .. + }) = piece + { + if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { + // If the span is already in the suggestion, we don't need to process it again + return; + } + + // lambda to get the decremented index based on the replaced positions + let decremented_index = |index: usize| -> usize { + let decrement = replaced_position.iter().filter(|&&i| i < index).count(); + index - decrement + }; + + suggestion.push((*span, decremented_index(*index).to_string())); + + // If there are format options, we need to handle them as well + if *format_options != FormatOptions::default() { + // lambda to process width and precision format counts and add them to the suggestion + let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { + if let Some(FormatCount::Argument(FormatArgPosition { + index: Ok(format_arg_index), + kind: FormatArgPositionKind::Number, + span: Some(format_arg_span), + })) = count + { + suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); + } + }; + + process_format_count(&format_options.width, &|index: usize| format!("{index}$")); + process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); + } + } +} + +/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw +/// +/// `r#"a"#` -> (`a`, true) +/// +/// `"b"` -> (`b`, false) +fn extract_str_literal(literal: &str) -> Option<(String, bool)> { + let (literal, raw) = match literal.strip_prefix('r') { + Some(stripped) => (stripped.trim_matches('#'), true), + None => (literal, false), + }; + + Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) +} + +enum UnescapeErr { + /// Should still be linted, can be manually resolved by author, e.g. + /// + /// ```ignore + /// print!(r"{}", '"'); + /// ``` + Lint, + /// Should not be linted, e.g. + /// + /// ```ignore + /// print!(r"{}", '\r'); + /// ``` + Ignore, +} + +/// Unescape a normal string into a raw string +fn conservative_unescape(literal: &str) -> Result { + let mut unescaped = String::with_capacity(literal.len()); + let mut chars = literal.chars(); + let mut err = false; + + while let Some(ch) = chars.next() { + match ch { + '#' => err = true, + '\\' => match chars.next() { + Some('\\') => unescaped.push('\\'), + Some('"') => err = true, + _ => return Err(UnescapeErr::Ignore), + }, + _ => unescaped.push(ch), + } + } + + if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } +} + +/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces +/// in `\u{xxxx}` are left unmodified +#[expect(clippy::match_same_arms)] +fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { + #[derive(Clone, Copy)] + enum State { + Normal, + Backslash, + UnicodeEscape, + } + + let mut escaped = String::with_capacity(literal.len()); + let mut state = State::Normal; + + for ch in literal.chars() { + state = match (ch, state) { + // Escape braces outside of unicode escapes by doubling them up + ('{' | '}', State::Normal) => { + escaped.push(ch); + State::Normal + }, + // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: + // + // \u{aaaa} \\ \x01 + // ^ ^ ^ + ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, + // \u{aaaa} + // ^ + ('u', State::Backslash) => State::UnicodeEscape, + // \xAA \\ + // ^ ^ + (_, State::Backslash) => State::Normal, + // \u{aaaa} + // ^ + ('}', State::UnicodeEscape) => State::Normal, + _ => state, + }; + + escaped.push(ch); + } + + escaped +} diff --git a/src/tools/clippy/clippy_lints/src/write/mod.rs b/src/tools/clippy/clippy_lints/src/write/mod.rs new file mode 100644 index 000000000000..c42c047745bb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write/mod.rs @@ -0,0 +1,354 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::macros::{FormatArgsStorage, root_macro_call_first_node}; +use clippy_utils::{is_in_test, sym}; +use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::impl_lint_pass; + +mod empty_string; +mod literal; +mod use_debug; +mod with_newline; + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `println!("")` to + /// print a newline. + /// + /// ### Why is this bad? + /// You should use `println!()`, which is simpler. + /// + /// ### Example + /// ```no_run + /// println!(""); + /// ``` + /// + /// Use instead: + /// ```no_run + /// println!(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINTLN_EMPTY_STRING, + style, + "using `println!(\"\")` with an empty string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `print!()` with a format + /// string that ends in a newline. + /// + /// ### Why is this bad? + /// You should use `println!()` instead, which appends the + /// newline. + /// + /// ### Example + /// ```no_run + /// # let name = "World"; + /// print!("Hello {}!\n", name); + /// ``` + /// use println!() instead + /// ```no_run + /// # let name = "World"; + /// println!("Hello {}!", name); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_WITH_NEWLINE, + style, + "using `print!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for printing on *stdout*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// ### Why restrict this? + /// People often print on *stdout* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// ### Known problems + /// Only catches `print!` and `println!` calls. + /// + /// ### Example + /// ```no_run + /// println!("Hello world!"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_STDOUT, + restriction, + "printing on stdout" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for printing on *stderr*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// ### Why restrict this? + /// People often print on *stderr* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// ### Known problems + /// Only catches `eprint!` and `eprintln!` calls. + /// + /// ### Example + /// ```no_run + /// eprintln!("Hello world!"); + /// ``` + #[clippy::version = "1.50.0"] + pub PRINT_STDERR, + restriction, + "printing on stderr" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Debug` formatting. The purpose of this + /// lint is to catch debugging remnants. + /// + /// ### Why restrict this? + /// The purpose of the `Debug` trait is to facilitate debugging Rust code, + /// and [no guarantees are made about its output][stability]. + /// It should not be used in user-facing output. + /// + /// ### Example + /// ```no_run + /// # let foo = "bar"; + /// println!("{:?}", foo); + /// ``` + /// + /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability + #[clippy::version = "pre 1.29.0"] + pub USE_DEBUG, + restriction, + "use of `Debug`-based formatting" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns about the use of literals as `print!`/`println!` args. + /// + /// ### Why is this bad? + /// Using literals as `println!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// ### Example + /// ```no_run + /// println!("{}", "foo"); + /// ``` + /// use the literal without formatting: + /// ```no_run + /// println!("foo"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_LITERAL, + style, + "printing a literal with a format string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `writeln!(buf, "")` to + /// print a newline. + /// + /// ### Why is this bad? + /// You should use `writeln!(buf)`, which is simpler. + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, ""); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITELN_EMPTY_STRING, + style, + "using `writeln!(buf, \"\")` with an empty string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `write!()` with a format + /// string that + /// ends in a newline. + /// + /// ### Why is this bad? + /// You should use `writeln!()` instead, which appends the + /// newline. + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// write!(buf, "Hello {}!\n", name); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// writeln!(buf, "Hello {}!", name); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITE_WITH_NEWLINE, + style, + "using `write!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns about the use of literals as `write!`/`writeln!` args. + /// + /// ### Why is this bad? + /// Using literals as `writeln!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "{}", "foo"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "foo"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITE_LITERAL, + style, + "writing a literal with a format string" +} + +pub struct Write { + format_args: FormatArgsStorage, + // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated + outermost_debug_impl: Option, + allow_print_in_tests: bool, +} + +impl Write { + pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { + Self { + format_args, + outermost_debug_impl: None, + allow_print_in_tests: conf.allow_print_in_tests, + } + } + + fn in_debug_impl(&self) -> bool { + self.outermost_debug_impl.is_some() + } +} + +impl_lint_pass!(Write => [ + PRINT_WITH_NEWLINE, + PRINTLN_EMPTY_STRING, + PRINT_STDOUT, + PRINT_STDERR, + USE_DEBUG, + PRINT_LITERAL, + WRITE_WITH_NEWLINE, + WRITELN_EMPTY_STRING, + WRITE_LITERAL, +]); + +impl<'tcx> LateLintPass<'tcx> for Write { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // Only check for `impl Debug`s if we're not already in one + if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { + self.outermost_debug_impl = Some(item.owner_id); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { + // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl + if self.outermost_debug_impl == Some(item.owner_id) { + self.outermost_debug_impl = None; + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { + return; + }; + let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { + return; + }; + let Some(name) = diag_name.as_str().strip_suffix("_macro") else { + return; + }; + + let is_build_script = cx + .sess() + .opts + .crate_name + .as_ref() + .is_some_and(|crate_name| crate_name == "build_script_build"); + + let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); + match diag_name { + sym::print_macro | sym::println_macro if !allowed_in_tests => { + if !is_build_script { + span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); + } + }, + sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { + span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); + }, + sym::write_macro | sym::writeln_macro => {}, + _ => return, + } + + if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { + // ignore `writeln!(w)` and `write!(v, some_macro!())` + if format_args.span.from_expansion() { + return; + } + + match diag_name { + sym::print_macro | sym::eprint_macro | sym::write_macro => { + with_newline::check(cx, format_args, ¯o_call, name); + }, + sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { + empty_string::check(cx, format_args, ¯o_call, name); + }, + _ => {}, + } + + literal::check(cx, format_args, name); + + if !self.in_debug_impl() { + use_debug::check(cx, format_args); + } + } + } +} + +fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { + if let ItemKind::Impl(Impl { + of_trait: Some(of_trait), + .. + }) = &item.kind + && let Some(trait_id) = of_trait.trait_ref.trait_def_id() + { + cx.tcx.is_diagnostic_item(sym::Debug, trait_id) + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/write/use_debug.rs b/src/tools/clippy/clippy_lints/src/write/use_debug.rs new file mode 100644 index 000000000000..75dddeb5d2a7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write/use_debug.rs @@ -0,0 +1,18 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::{FormatArgs, FormatArgsPiece, FormatPlaceholder, FormatTrait}; +use rustc_lint::LateContext; + +use super::USE_DEBUG; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs) { + for piece in &format_args.template { + if let &FormatArgsPiece::Placeholder(FormatPlaceholder { + span: Some(span), + format_trait: FormatTrait::Debug, + .. + }) = piece + { + span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/write/with_newline.rs b/src/tools/clippy/clippy_lints/src/write/with_newline.rs new file mode 100644 index 000000000000..e4b51da3cadc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write/with_newline.rs @@ -0,0 +1,78 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, LintContext}; +use rustc_span::BytePos; + +use super::{PRINT_WITH_NEWLINE, WRITE_WITH_NEWLINE}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { + return; + }; + + let count_vertical_whitespace = || { + format_args + .template + .iter() + .filter_map(|piece| match piece { + FormatArgsPiece::Literal(literal) => Some(literal), + FormatArgsPiece::Placeholder(_) => None, + }) + .flat_map(|literal| literal.as_str().chars()) + .filter(|ch| matches!(ch, '\r' | '\n')) + .count() + }; + + if last.as_str().ends_with('\n') + // ignore format strings with other internal vertical whitespace + && count_vertical_whitespace() == 1 + { + let mut format_string_span = format_args.span; + + let lint = if name == "write" { + format_string_span = expand_past_previous_comma(cx, format_string_span); + + WRITE_WITH_NEWLINE + } else { + PRINT_WITH_NEWLINE + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("using `{name}!()` with a format string that ends in a single newline"), + |diag| { + let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + let Some(format_snippet) = format_string_span.get_source_text(cx) else { + return; + }; + + if format_args.template.len() == 1 && last == sym::LF { + // print!("\n"), write!(f, "\n") + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], + Applicability::MachineApplicable, + ); + } else if format_snippet.ends_with("\\n\"") { + // print!("...\n"), write!(f, "...\n") + + let hi = format_string_span.hi(); + let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (newline_span, String::new())], + Applicability::MachineApplicable, + ); + } + }, + ); + } +} diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md index 45463b4fa1db..6f976094fc2d 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: ``` -nightly-2025-10-31 +nightly-2025-11-14 ``` 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 208aa98f12f2..9c08f7b4d80f 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -371,7 +371,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && both(lb.as_ref(), rb.as_ref(), |l, r| eq_const_item_rhs(l, r)) + && both(lb.as_ref(), rb.as_ref(), eq_const_item_rhs) }, ( Fn(box ast::Fn { @@ -625,7 +625,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && both(lb.as_ref(), rb.as_ref(), |l, r| eq_const_item_rhs(l, r)) + && both(lb.as_ref(), rb.as_ref(), eq_const_item_rhs) }, ( Fn(box ast::Fn { diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs index 2d42e76dcbc9..671b266ba008 100644 --- a/src/tools/clippy/clippy_utils/src/attrs.rs +++ b/src/tools/clippy/clippy_utils/src/attrs.rs @@ -1,3 +1,5 @@ +//! Utility functions for attributes, including Clippy's built-in ones + use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; use rustc_ast::attr; @@ -12,131 +14,59 @@ use rustc_span::{Span, Symbol}; use std::str::FromStr; -/// Deprecation status of attributes known by Clippy. -pub enum DeprecationStatus { - /// Attribute is deprecated - Deprecated, - /// Attribute is deprecated and was replaced by the named attribute - Replaced(&'static str), - None, -} - -#[rustfmt::skip] -pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[ - (sym::author, DeprecationStatus::None), - (sym::version, DeprecationStatus::None), - (sym::cognitive_complexity, DeprecationStatus::None), - (sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")), - (sym::dump, DeprecationStatus::None), - (sym::msrv, DeprecationStatus::None), - // The following attributes are for the 3rd party crate authors. - // See book/src/attribs.md - (sym::has_significant_drop, DeprecationStatus::None), - (sym::format_args, DeprecationStatus::None), -]; - -pub struct LimitStack { - stack: Vec, -} - -impl Drop for LimitStack { - fn drop(&mut self) { - assert_eq!(self.stack.len(), 1); - } -} - -impl LimitStack { - #[must_use] - pub fn new(limit: u64) -> Self { - Self { stack: vec![limit] } - } - pub fn limit(&self) -> u64 { - *self.stack.last().expect("there should always be a value in the stack") - } - pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| stack.push(val)); - } - pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val))); - } -} - -pub fn get_attr<'a, A: AttributeExt + 'a>( +/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name` +pub fn get_builtin_attr<'a, A: AttributeExt + 'a>( sess: &'a Session, attrs: &'a [A], name: Symbol, ) -> impl Iterator { attrs.iter().filter(move |attr| { - let Some(attr_segments) = attr.ident_path() else { - return false; - }; + if let Some([clippy, segment2]) = attr.ident_path().as_deref() + && clippy.name == sym::clippy + { + let new_name = match segment2.name { + sym::cyclomatic_complexity => Some("cognitive_complexity"), + sym::author + | sym::version + | sym::cognitive_complexity + | sym::dump + | sym::msrv + // The following attributes are for the 3rd party crate authors. + // See book/src/attribs.md + | sym::has_significant_drop + | sym::format_args => None, + _ => { + sess.dcx().span_err(segment2.span, "usage of unknown attribute"); + return false; + }, + }; - if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy { - BUILTIN_ATTRIBUTES - .iter() - .find_map(|(builtin_name, deprecation_status)| { - if attr_segments[1].name == *builtin_name { - Some(deprecation_status) - } else { - None - } - }) - .map_or_else( - || { - sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute"); - false - }, - |deprecation_status| { - let mut diag = sess - .dcx() - .struct_span_err(attr_segments[1].span, "usage of deprecated attribute"); - match *deprecation_status { - DeprecationStatus::Deprecated => { - diag.emit(); - false - }, - DeprecationStatus::Replaced(new_name) => { - diag.span_suggestion( - attr_segments[1].span, - "consider using", - new_name, - Applicability::MachineApplicable, - ); - diag.emit(); - false - }, - DeprecationStatus::None => { - diag.cancel(); - attr_segments[1].name == name - }, - } - }, - ) + match new_name { + Some(new_name) => { + sess.dcx() + .struct_span_err(segment2.span, "usage of deprecated attribute") + .with_span_suggestion( + segment2.span, + "consider using", + new_name, + Applicability::MachineApplicable, + ) + .emit(); + false + }, + None => segment2.name == name, + } } else { false } }) } -fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { - for attr in get_attr(sess, attrs, name) { - if let Some(value) = attr.value_str() { - if let Ok(value) = FromStr::from_str(value.as_str()) { - f(value); - } else { - sess.dcx().span_err(attr.span(), "not a number"); - } - } else { - sess.dcx().span_err(attr.span(), "bad clippy attribute"); - } - } -} - -pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { +/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`, +/// returns that attribute, and `None` otherwise +pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { let mut unique_attr: Option<&A> = None; - for attr in get_attr(sess, attrs, name) { + for attr in get_builtin_attr(sess, attrs, name) { if let Some(duplicate) = unique_attr { sess.dcx() .struct_span_err(attr.span(), format!("`{name}` is defined multiple times")) @@ -149,13 +79,13 @@ pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], n unique_attr } -/// Returns true if the attributes contain any of `proc_macro`, -/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise +/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or +/// `proc_macro_attribute` pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool { attrs.iter().any(AttributeExt::is_proc_macro_attr) } -/// Returns true if the attributes contain `#[doc(hidden)]` +/// Checks whether `attrs` contain `#[doc(hidden)]` pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { attrs .iter() @@ -164,6 +94,7 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { .any(|l| attr::list_contains_name(&l, sym::hidden)) } +/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]` pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { adt.is_variant_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..)) @@ -176,7 +107,7 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..))) } -/// Checks if the given span contains a `#[cfg(..)]` attribute +/// Checks whether the given span contains a `#[cfg(..)]` attribute pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { s.check_source_text(cx, |src| { let mut iter = tokenize_with_text(src); @@ -198,3 +129,52 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { false }) } + +/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]` +pub struct LimitStack { + default: u64, + stack: Vec, +} + +impl Drop for LimitStack { + fn drop(&mut self) { + debug_assert_eq!(self.stack, Vec::::new()); // avoid `.is_empty()`, for a nicer error message + } +} + +#[expect(missing_docs, reason = "they're all trivial...")] +impl LimitStack { + #[must_use] + /// Initialize the stack starting with a default value, which usually comes from configuration + pub fn new(limit: u64) -> Self { + Self { + default: limit, + stack: vec![], + } + } + pub fn limit(&self) -> u64 { + self.stack.last().copied().unwrap_or(self.default) + } + pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| stack.push(val)); + } + pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val))); + } +} + +fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { + for attr in get_builtin_attr(sess, attrs, name) { + let Some(value) = attr.value_str() else { + sess.dcx().span_err(attr.span(), "bad clippy attribute"); + continue; + }; + let Ok(value) = u64::from_str(value.as_str()) else { + sess.dcx().span_err(attr.span(), "not a number"); + continue; + }; + f(value); + } +} diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index ac408a1b59e5..7e3fa4f9909b 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -1138,9 +1138,8 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx match ct_rhs { ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { - ConstArgKind::Path(_) => None, ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), - ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, + ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, }, } } diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 2433ca8b97f2..b286701fbed1 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -480,10 +480,8 @@ fn eq_const_arg(&mut self, left: &ConstArg<'_>, right: &ConstArg<'_>) -> bool { // Use explicit match for now since ConstArg is undergoing flux. (ConstArgKind::Path(..), ConstArgKind::Anon(..)) | (ConstArgKind::Anon(..), ConstArgKind::Path(..)) - | (ConstArgKind::Infer(..), _) - | (_, ConstArgKind::Infer(..)) - | (ConstArgKind::Error(..), _) - | (_, ConstArgKind::Error(..)) => false, + | (ConstArgKind::Infer(..) | ConstArgKind::Error(..), _) + | (_, ConstArgKind::Infer(..) | ConstArgKind::Error(..)) => false, } } diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index d98e3073b41d..c9302b17eb7e 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -50,6 +50,7 @@ extern crate rustc_trait_selection; pub mod ast_utils; +#[deny(missing_docs)] pub mod attrs; mod check_proc_macro; pub mod comparisons; @@ -131,7 +132,7 @@ use crate::consts::{ConstEvalCtxt, Constant}; use crate::higher::Range; use crate::msrvs::Msrv; -use crate::res::{MaybeDef, MaybeResPath}; +use crate::res::{MaybeDef, MaybeQPath, MaybeResPath}; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr_without_closures; @@ -300,6 +301,22 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> cx.tcx.lang_items().get(item) == Some(did) } +/// Checks is `expr` is `None` +pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +} + +/// If `expr` is `Some(inner)`, returns `inner` +pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call(e, [arg]) = expr.kind + && e.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + { + Some(arg) + } else { + None + } +} + /// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( @@ -320,6 +337,25 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } +/// If `pat` is: +/// - `Some(inner)`, returns `inner` +/// - it will _usually_ contain just one element, but could have two, given patterns like +/// `Some(inner, ..)` or `Some(.., inner)` +/// - `Some`, returns `[]` +/// - otherwise, returns `None` +pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> { + if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind + && cx + .qpath_res(qpath, pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionSome) + { + Some(inner) + } else { + None + } +} + /// Checks if the `pat` is `None`. pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { matches!(pat.kind, @@ -2782,11 +2818,7 @@ pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( pat: &'a Pat<'hir>, else_body: &Expr<'_>, ) -> Option<&'a Pat<'hir>> { - if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind - && cx - .qpath_res(&pat_path, pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionSome) + if let Some([inner_pat]) = as_some_pattern(cx, pat) && !is_refutable(cx, inner_pat) && let else_body = peel_blocks(else_body) && let ExprKind::Ret(Some(ret_val)) = else_body.kind diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index 7cd5a16f5b46..4e06f010bd59 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock}; use crate::visitors::{Descend, for_each_expr_without_closures}; -use crate::{get_unique_attr, sym}; +use crate::{get_unique_builtin_attr, sym}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; @@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { } else { // Allow users to tag any macro as being format!-like // TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method - get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() + get_unique_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() } } diff --git a/src/tools/clippy/clippy_utils/src/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs index bb2a62821100..b5fffab13b1d 100644 --- a/src/tools/clippy/clippy_utils/src/numeric_literal.rs +++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs @@ -1,11 +1,16 @@ use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; use std::iter; +/// Represents the base of a numeric literal, used for parsing and formatting. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Radix { + /// A binary literal (e.g., `0b1010`) Binary, + /// An octal literal (e.g., `0o670`) Octal, + /// A decimal literal (e.g., `123`) Decimal, + /// A hexadecimal literal (e.g., `0xFF`) Hexadecimal, } @@ -46,6 +51,7 @@ pub struct NumericLiteral<'a> { } impl<'a> NumericLiteral<'a> { + /// Attempts to parse a `NumericLiteral` from the source string of an `ast::LitKind`. pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option> { let unsigned_src = src.strip_prefix('-').map_or(src, |s| s); if lit_kind.is_numeric() @@ -63,6 +69,7 @@ pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option, float: bool) -> Self { let unsigned_lit = lit.trim_start_matches('-'); @@ -102,11 +109,12 @@ pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self { } } + /// Checks if the literal's radix is `Radix::Decimal` pub fn is_decimal(&self) -> bool { self.radix == Radix::Decimal } - pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) { + fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) { let mut integer = digits; let mut fraction = None; let mut exponent = None; @@ -180,7 +188,7 @@ pub fn format(&self) -> String { output } - pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) { + fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, zero_pad: bool) { debug_assert!(group_size > 0); let mut digits = input.chars().filter(|&c| c != '_'); @@ -196,7 +204,7 @@ pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial if partial_group_first { first_group_size = (digits.clone().count() - 1) % group_size + 1; - if pad { + if zero_pad { for _ in 0..group_size - first_group_size { output.push('0'); } 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 0cf1ad348953..296da9fec8c0 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 @@ -194,8 +194,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) - | Rvalue::ShallowInitBox(_, _) => Ok(()), + Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, cx.tcx); if ty.is_integral() || ty.is_bool() { diff --git a/src/tools/clippy/clippy_utils/src/sym.rs b/src/tools/clippy/clippy_utils/src/sym.rs index c2523540f007..1d1537dd0e91 100644 --- a/src/tools/clippy/clippy_utils/src/sym.rs +++ b/src/tools/clippy/clippy_utils/src/sym.rs @@ -126,6 +126,7 @@ macro_rules! generate { copy_from_nonoverlapping, copy_to, copy_to_nonoverlapping, + core_arch, count_ones, create, create_new, @@ -330,6 +331,7 @@ macro_rules! generate { splitn_mut, sqrt, starts_with, + std_detect, step_by, strlen, style, diff --git a/src/tools/clippy/rust-toolchain.toml b/src/tools/clippy/rust-toolchain.toml index d23fd74d9acc..f6809da98f2b 100644 --- a/src/tools/clippy/rust-toolchain.toml +++ b/src/tools/clippy/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-10-31" +channel = "nightly-2025-11-14" # 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/arithmetic_side_effects.rs b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs index 3245b2c983e1..b7ed596d811e 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs @@ -17,6 +17,7 @@ extern crate proc_macro_derive; use core::num::{NonZero, Saturating, Wrapping}; +use core::time::Duration; const ONE: i32 = 1; const ZERO: i32 = 0; @@ -687,4 +688,59 @@ pub fn explicit_methods() { //~^ arithmetic_side_effects } +pub fn issue_15943(days: u8) -> Duration { + Duration::from_secs(86400 * u64::from(days)) +} + +pub fn type_conversion_add() { + let _ = u128::MAX + u128::from(1u8); + //~^ arithmetic_side_effects + let _ = 1u128 + u128::from(1u16); + let _ = 1u128 + u128::from(1u32); + let _ = 1u128 + u128::from(1u64); + + let _ = 1u64 + u64::from(1u8); + let _ = 1u64 + u64::from(1u16); + let _ = 1u64 + u64::from(1u32); + + let _ = 1u32 + u32::from(1u8); + let _ = 1u32 + u32::from(1u16); + + let _ = 1u16 + u16::from(1u8); +} + +pub fn type_conversion_mul() { + let _ = u128::MAX * u128::from(1u8); + //~^ arithmetic_side_effects + let _ = 1u128 * u128::from(1u16); + let _ = 1u128 * u128::from(1u32); + let _ = 1u128 * u128::from(1u64); + + let _ = 1u64 * u64::from(1u8); + let _ = 1u64 * u64::from(1u16); + let _ = 1u64 * u64::from(1u32); + + let _ = 1u32 * u32::from(1u8); + let _ = 1u32 * u32::from(1u16); + + let _ = 1u16 * u16::from(1u8); +} + +pub fn type_conversion_does_not_escape_its_context() { + struct Foo; + impl Foo { + fn from(n: u8) -> u64 { + u64::from(n) + } + } + let _ = Duration::from_secs(86400 * Foo::from(1)); + //~^ arithmetic_side_effects + + fn shift(x: u8) -> u64 { + 1 << u64::from(x) + } + let _ = Duration::from_secs(86400 * shift(1)); + //~^ arithmetic_side_effects +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr b/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr index 4150493ba94a..22742a82601a 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr @@ -1,5 +1,5 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:166:13 + --> tests/ui/arithmetic_side_effects.rs:167:13 | LL | let _ = 1f16 + 1f16; | ^^^^^^^^^^^ @@ -8,766 +8,790 @@ LL | let _ = 1f16 + 1f16; = help: to override `-D warnings` add `#[allow(clippy::arithmetic_side_effects)]` error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:170:13 + --> tests/ui/arithmetic_side_effects.rs:171:13 | LL | let _ = 1f128 + 1f128; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:175:13 + --> tests/ui/arithmetic_side_effects.rs:176:13 | LL | let _ = String::new() + &String::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:311:5 + --> tests/ui/arithmetic_side_effects.rs:312:5 | LL | _n += 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:313:5 + --> tests/ui/arithmetic_side_effects.rs:314:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:315:5 + --> tests/ui/arithmetic_side_effects.rs:316:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:317:5 + --> tests/ui/arithmetic_side_effects.rs:318:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:319:5 + --> tests/ui/arithmetic_side_effects.rs:320:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:321:5 + --> tests/ui/arithmetic_side_effects.rs:322:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:323:5 + --> tests/ui/arithmetic_side_effects.rs:324:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:325:5 + --> tests/ui/arithmetic_side_effects.rs:326:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:327:5 + --> tests/ui/arithmetic_side_effects.rs:328:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:329:5 + --> tests/ui/arithmetic_side_effects.rs:330:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:331:5 + --> tests/ui/arithmetic_side_effects.rs:332:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:333:5 + --> tests/ui/arithmetic_side_effects.rs:334:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:335:5 + --> tests/ui/arithmetic_side_effects.rs:336:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:337:5 + --> tests/ui/arithmetic_side_effects.rs:338:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:339:5 + --> tests/ui/arithmetic_side_effects.rs:340:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:341:5 + --> tests/ui/arithmetic_side_effects.rs:342:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:343:5 + --> tests/ui/arithmetic_side_effects.rs:344:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:345:5 + --> tests/ui/arithmetic_side_effects.rs:346:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:347:5 + --> tests/ui/arithmetic_side_effects.rs:348:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:349:5 + --> tests/ui/arithmetic_side_effects.rs:350:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:351:5 + --> tests/ui/arithmetic_side_effects.rs:352:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:353:5 + --> tests/ui/arithmetic_side_effects.rs:354:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:355:5 + --> tests/ui/arithmetic_side_effects.rs:356:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:357:5 + --> tests/ui/arithmetic_side_effects.rs:358:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:359:5 + --> tests/ui/arithmetic_side_effects.rs:360:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:361:5 + --> tests/ui/arithmetic_side_effects.rs:362:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:363:5 + --> tests/ui/arithmetic_side_effects.rs:364:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:365:5 + --> tests/ui/arithmetic_side_effects.rs:366:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:367:5 + --> tests/ui/arithmetic_side_effects.rs:368:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:369:5 + --> tests/ui/arithmetic_side_effects.rs:370:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:371:5 + --> tests/ui/arithmetic_side_effects.rs:372:5 | LL | _custom >>= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:373:5 + --> tests/ui/arithmetic_side_effects.rs:374:5 | LL | _custom >>= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:375:5 + --> tests/ui/arithmetic_side_effects.rs:376:5 | LL | _custom <<= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:377:5 + --> tests/ui/arithmetic_side_effects.rs:378:5 | LL | _custom <<= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:379:5 + --> tests/ui/arithmetic_side_effects.rs:380:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:381:5 + --> tests/ui/arithmetic_side_effects.rs:382:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:383:5 + --> tests/ui/arithmetic_side_effects.rs:384:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:385:5 + --> tests/ui/arithmetic_side_effects.rs:386:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:387:5 + --> tests/ui/arithmetic_side_effects.rs:388:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:389:5 + --> tests/ui/arithmetic_side_effects.rs:390:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:391:5 + --> tests/ui/arithmetic_side_effects.rs:392:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:393:5 + --> tests/ui/arithmetic_side_effects.rs:394:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:395:5 + --> tests/ui/arithmetic_side_effects.rs:396:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:397:5 + --> tests/ui/arithmetic_side_effects.rs:398:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:399:5 + --> tests/ui/arithmetic_side_effects.rs:400:5 | LL | _custom >>= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:401:5 + --> tests/ui/arithmetic_side_effects.rs:402:5 | LL | _custom >>= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:403:5 + --> tests/ui/arithmetic_side_effects.rs:404:5 | LL | _custom <<= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:405:5 + --> tests/ui/arithmetic_side_effects.rs:406:5 | LL | _custom <<= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:409:10 + --> tests/ui/arithmetic_side_effects.rs:410:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:411:10 + --> tests/ui/arithmetic_side_effects.rs:412:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:413:10 + --> tests/ui/arithmetic_side_effects.rs:414:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:415:10 + --> tests/ui/arithmetic_side_effects.rs:416:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:417:10 + --> tests/ui/arithmetic_side_effects.rs:418:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:419:10 + --> tests/ui/arithmetic_side_effects.rs:420:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:421:10 + --> tests/ui/arithmetic_side_effects.rs:422:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:423:10 + --> tests/ui/arithmetic_side_effects.rs:424:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:425:10 + --> tests/ui/arithmetic_side_effects.rs:426:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:427:10 + --> tests/ui/arithmetic_side_effects.rs:428:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:429:10 + --> tests/ui/arithmetic_side_effects.rs:430:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:431:10 + --> tests/ui/arithmetic_side_effects.rs:432:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:433:10 + --> tests/ui/arithmetic_side_effects.rs:434:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:435:10 + --> tests/ui/arithmetic_side_effects.rs:436:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:437:10 + --> tests/ui/arithmetic_side_effects.rs:438:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:439:10 + --> tests/ui/arithmetic_side_effects.rs:440:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:441:10 + --> tests/ui/arithmetic_side_effects.rs:442:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:443:10 + --> tests/ui/arithmetic_side_effects.rs:444:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:445:10 + --> tests/ui/arithmetic_side_effects.rs:446:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:447:15 + --> tests/ui/arithmetic_side_effects.rs:448:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:449:15 + --> tests/ui/arithmetic_side_effects.rs:450:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:451:15 + --> tests/ui/arithmetic_side_effects.rs:452:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:453:15 + --> tests/ui/arithmetic_side_effects.rs:454:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:455:15 + --> tests/ui/arithmetic_side_effects.rs:456:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:457:15 + --> tests/ui/arithmetic_side_effects.rs:458:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:459:15 + --> tests/ui/arithmetic_side_effects.rs:460:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:461:15 + --> tests/ui/arithmetic_side_effects.rs:462:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:463:15 + --> tests/ui/arithmetic_side_effects.rs:464:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:465:15 + --> tests/ui/arithmetic_side_effects.rs:466:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:467:15 + --> tests/ui/arithmetic_side_effects.rs:468:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:469:15 + --> tests/ui/arithmetic_side_effects.rs:470:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:471:15 + --> tests/ui/arithmetic_side_effects.rs:472:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:473:15 + --> tests/ui/arithmetic_side_effects.rs:474:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:475:15 + --> tests/ui/arithmetic_side_effects.rs:476:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:477:15 + --> tests/ui/arithmetic_side_effects.rs:478:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:479:15 + --> tests/ui/arithmetic_side_effects.rs:480:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:481:15 + --> tests/ui/arithmetic_side_effects.rs:482:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:483:15 + --> tests/ui/arithmetic_side_effects.rs:484:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:485:15 + --> tests/ui/arithmetic_side_effects.rs:486:15 | LL | _custom = _custom >> _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:487:15 + --> tests/ui/arithmetic_side_effects.rs:488:15 | LL | _custom = _custom >> &_custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:489:15 + --> tests/ui/arithmetic_side_effects.rs:490:15 | LL | _custom = Custom << _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:491:15 + --> tests/ui/arithmetic_side_effects.rs:492:15 | LL | _custom = &Custom << _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:495:23 + --> tests/ui/arithmetic_side_effects.rs:496:23 | LL | _n.saturating_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:497:21 + --> tests/ui/arithmetic_side_effects.rs:498:21 | LL | _n.wrapping_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:499:21 + --> tests/ui/arithmetic_side_effects.rs:500:21 | LL | _n.wrapping_rem(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:501:28 + --> tests/ui/arithmetic_side_effects.rs:502:28 | LL | _n.wrapping_rem_euclid(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:504:23 + --> tests/ui/arithmetic_side_effects.rs:505:23 | LL | _n.saturating_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:506:21 + --> tests/ui/arithmetic_side_effects.rs:507:21 | LL | _n.wrapping_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:508:21 + --> tests/ui/arithmetic_side_effects.rs:509:21 | LL | _n.wrapping_rem(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:510:28 + --> tests/ui/arithmetic_side_effects.rs:511:28 | LL | _n.wrapping_rem_euclid(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:513:23 + --> tests/ui/arithmetic_side_effects.rs:514:23 | LL | _n.saturating_div(*Box::new(_n)); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:517:10 + --> tests/ui/arithmetic_side_effects.rs:518:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:519:10 + --> tests/ui/arithmetic_side_effects.rs:520:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:521:15 + --> tests/ui/arithmetic_side_effects.rs:522:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:523:15 + --> tests/ui/arithmetic_side_effects.rs:524:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:525:9 + --> tests/ui/arithmetic_side_effects.rs:526:9 | LL | _ = -*Box::new(_n); | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:535:5 + --> tests/ui/arithmetic_side_effects.rs:536:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:537:5 + --> tests/ui/arithmetic_side_effects.rs:538:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:539:5 + --> tests/ui/arithmetic_side_effects.rs:540:5 | LL | 1 % i / 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:541:5 + --> tests/ui/arithmetic_side_effects.rs:542:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:543:5 + --> tests/ui/arithmetic_side_effects.rs:544:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:555:5 + --> tests/ui/arithmetic_side_effects.rs:556:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:557:5 + --> tests/ui/arithmetic_side_effects.rs:558:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:559:5 + --> tests/ui/arithmetic_side_effects.rs:560:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:562:5 + --> tests/ui/arithmetic_side_effects.rs:563:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:565:5 + --> tests/ui/arithmetic_side_effects.rs:566:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:567:5 + --> tests/ui/arithmetic_side_effects.rs:568:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:570:5 + --> tests/ui/arithmetic_side_effects.rs:571:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:573:5 + --> tests/ui/arithmetic_side_effects.rs:574:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:575:5 + --> tests/ui/arithmetic_side_effects.rs:576:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:586:5 + --> tests/ui/arithmetic_side_effects.rs:587:5 | LL | 10 / a | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:641:9 + --> tests/ui/arithmetic_side_effects.rs:642:9 | LL | x / maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:646:9 + --> tests/ui/arithmetic_side_effects.rs:647:9 | LL | x % maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:658:5 + --> tests/ui/arithmetic_side_effects.rs:659:5 | LL | one.add_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:663:5 + --> tests/ui/arithmetic_side_effects.rs:664:5 | LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:684:5 + --> tests/ui/arithmetic_side_effects.rs:685:5 | LL | one.add(&one); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:686:5 + --> tests/ui/arithmetic_side_effects.rs:687:5 | LL | Box::new(one).add(one); | ^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 128 previous errors +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:696:13 + | +LL | let _ = u128::MAX + u128::from(1u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:713:13 + | +LL | let _ = u128::MAX * u128::from(1u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:736:33 + | +LL | let _ = Duration::from_secs(86400 * Foo::from(1)); + | ^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:742:33 + | +LL | let _ = Duration::from_secs(86400 * shift(1)); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 132 previous errors diff --git a/src/tools/clippy/tests/ui/cmp_null.fixed b/src/tools/clippy/tests/ui/cmp_null.fixed index 04b8ec50160b..c12279cf12e6 100644 --- a/src/tools/clippy/tests/ui/cmp_null.fixed +++ b/src/tools/clippy/tests/ui/cmp_null.fixed @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m.is_null() { //~^ cmp_null diff --git a/src/tools/clippy/tests/ui/cmp_null.rs b/src/tools/clippy/tests/ui/cmp_null.rs index 6f7762e6ae83..2771a16e00c5 100644 --- a/src/tools/clippy/tests/ui/cmp_null.rs +++ b/src/tools/clippy/tests/ui/cmp_null.rs @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m == ptr::null_mut() { //~^ cmp_null diff --git a/src/tools/clippy/tests/ui/cmp_null.stderr b/src/tools/clippy/tests/ui/cmp_null.stderr index 8a75b0501119..381747cb3c65 100644 --- a/src/tools/clippy/tests/ui/cmp_null.stderr +++ b/src/tools/clippy/tests/ui/cmp_null.stderr @@ -1,5 +1,5 @@ error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:9:8 + --> tests/ui/cmp_null.rs:8:8 | LL | if p == ptr::null() { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` @@ -8,31 +8,31 @@ LL | if p == ptr::null() { = 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:14:8 + --> tests/ui/cmp_null.rs:13: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:22:8 + --> 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:27:8 + --> tests/ui/cmp_null.rs:26: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:33:13 + --> tests/ui/cmp_null.rs:32:13 | LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:39:19 + --> tests/ui/cmp_null.rs:38:19 | LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast.32bit.stderr b/src/tools/clippy/tests/ui/fn_to_numeric_cast.32bit.stderr index 2affd0b7d6e9..86c189cb44cb 100644 --- a/src/tools/clippy/tests/ui/fn_to_numeric_cast.32bit.stderr +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast.32bit.stderr @@ -1,5 +1,5 @@ error: casting function pointer `foo` to `i8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:12:13 + --> tests/ui/fn_to_numeric_cast.rs:13:13 | LL | let _ = foo as i8; | ^^^^^^^^^ help: try: `foo as usize` @@ -8,13 +8,13 @@ LL | let _ = foo as i8; = help: to override `-D warnings` add `#[allow(clippy::fn_to_numeric_cast_with_truncation)]` error: casting function pointer `foo` to `i16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:14:13 + --> tests/ui/fn_to_numeric_cast.rs:15:13 | LL | let _ = foo as i16; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `i32` - --> tests/ui/fn_to_numeric_cast.rs:16:13 + --> tests/ui/fn_to_numeric_cast.rs:17:13 | LL | let _ = foo as i32; | ^^^^^^^^^^ help: try: `foo as usize` @@ -23,121 +23,121 @@ LL | let _ = foo as i32; = help: to override `-D warnings` add `#[allow(clippy::fn_to_numeric_cast)]` error: casting function pointer `foo` to `i64` - --> tests/ui/fn_to_numeric_cast.rs:19:13 + --> tests/ui/fn_to_numeric_cast.rs:20:13 | LL | let _ = foo as i64; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `i128` - --> tests/ui/fn_to_numeric_cast.rs:21:13 + --> tests/ui/fn_to_numeric_cast.rs:22:13 | LL | let _ = foo as i128; | ^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `isize` - --> tests/ui/fn_to_numeric_cast.rs:23:13 + --> tests/ui/fn_to_numeric_cast.rs:24:13 | LL | let _ = foo as isize; | ^^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:26:13 + --> tests/ui/fn_to_numeric_cast.rs:27:13 | LL | let _ = foo as u8; | ^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:28:13 + --> tests/ui/fn_to_numeric_cast.rs:29:13 | LL | let _ = foo as u16; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u32` - --> tests/ui/fn_to_numeric_cast.rs:30:13 + --> tests/ui/fn_to_numeric_cast.rs:31:13 | LL | let _ = foo as u32; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u64` - --> tests/ui/fn_to_numeric_cast.rs:33:13 + --> tests/ui/fn_to_numeric_cast.rs:34:13 | LL | let _ = foo as u64; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u128` - --> tests/ui/fn_to_numeric_cast.rs:35:13 + --> tests/ui/fn_to_numeric_cast.rs:36:13 | LL | let _ = foo as u128; | ^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `abc` to `i8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:49:13 + --> tests/ui/fn_to_numeric_cast.rs:50:13 | LL | let _ = abc as i8; | ^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:51:13 + --> tests/ui/fn_to_numeric_cast.rs:52:13 | LL | let _ = abc as i16; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i32` - --> tests/ui/fn_to_numeric_cast.rs:53:13 + --> tests/ui/fn_to_numeric_cast.rs:54:13 | LL | let _ = abc as i32; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i64` - --> tests/ui/fn_to_numeric_cast.rs:56:13 + --> tests/ui/fn_to_numeric_cast.rs:57:13 | LL | let _ = abc as i64; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i128` - --> tests/ui/fn_to_numeric_cast.rs:58:13 + --> tests/ui/fn_to_numeric_cast.rs:59:13 | LL | let _ = abc as i128; | ^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `isize` - --> tests/ui/fn_to_numeric_cast.rs:60:13 + --> tests/ui/fn_to_numeric_cast.rs:61:13 | LL | let _ = abc as isize; | ^^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:63:13 + --> tests/ui/fn_to_numeric_cast.rs:64:13 | LL | let _ = abc as u8; | ^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:65:13 + --> tests/ui/fn_to_numeric_cast.rs:66:13 | LL | let _ = abc as u16; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u32` - --> tests/ui/fn_to_numeric_cast.rs:67:13 + --> tests/ui/fn_to_numeric_cast.rs:68:13 | LL | let _ = abc as u32; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u64` - --> tests/ui/fn_to_numeric_cast.rs:70:13 + --> tests/ui/fn_to_numeric_cast.rs:71:13 | LL | let _ = abc as u64; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u128` - --> tests/ui/fn_to_numeric_cast.rs:72:13 + --> tests/ui/fn_to_numeric_cast.rs:73:13 | LL | let _ = abc as u128; | ^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `f` to `i32` - --> tests/ui/fn_to_numeric_cast.rs:80:5 + --> tests/ui/fn_to_numeric_cast.rs:81:5 | LL | f as i32 | ^^^^^^^^ help: try: `f as usize` diff --git a/src/tools/clippy/tests/ui/incompatible_msrv.rs b/src/tools/clippy/tests/ui/incompatible_msrv.rs index 3069c8139abe..e08828b46c36 100644 --- a/src/tools/clippy/tests/ui/incompatible_msrv.rs +++ b/src/tools/clippy/tests/ui/incompatible_msrv.rs @@ -178,4 +178,11 @@ const fn uncalled_len() { //~^ incompatible_msrv } +#[clippy::msrv = "1.0.0"] +fn vec_macro() { + let _: Vec = vec![]; + let _: Vec = vec![1; 3]; + let _: Vec = vec![1, 2]; +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/let_and_return.edition2021.fixed b/src/tools/clippy/tests/ui/let_and_return.edition2021.fixed index 70d503018e0f..e89e4476bf82 100644 --- a/src/tools/clippy/tests/ui/let_and_return.edition2021.fixed +++ b/src/tools/clippy/tests/ui/let_and_return.edition2021.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/let_and_return.edition2024.fixed b/src/tools/clippy/tests/ui/let_and_return.edition2024.fixed index 9990c3b71205..d2c76673ca03 100644 --- a/src/tools/clippy/tests/ui/let_and_return.edition2024.fixed +++ b/src/tools/clippy/tests/ui/let_and_return.edition2024.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/let_and_return.rs b/src/tools/clippy/tests/ui/let_and_return.rs index 48c20cdd60db..1af5f8ba5c16 100644 --- a/src/tools/clippy/tests/ui/let_and_return.rs +++ b/src/tools/clippy/tests/ui/let_and_return.rs @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/let_if_seq.rs b/src/tools/clippy/tests/ui/let_if_seq.rs index 2db206212aa5..69d6319fa8bf 100644 --- a/src/tools/clippy/tests/ui/let_if_seq.rs +++ b/src/tools/clippy/tests/ui/let_if_seq.rs @@ -139,3 +139,34 @@ fn main() { } println!("{}", val.get()); } + +fn issue16062(bar: fn() -> bool) { + let foo; + //~^ useless_let_if_seq + if bar() { + foo = 42; + } else { + foo = 0; + } +} + +fn issue16064(bar: fn() -> bool) { + macro_rules! mac { + ($e:expr) => { + $e() + }; + ($base:expr, $lit:expr) => { + $lit * $base + 2 + }; + } + + let foo; + //~^ useless_let_if_seq + if mac!(bar) { + foo = mac!(10, 4); + } else { + foo = 0; + } + + let bar = 1; +} diff --git a/src/tools/clippy/tests/ui/let_if_seq.stderr b/src/tools/clippy/tests/ui/let_if_seq.stderr index f59d42bf4c8d..b86bca6b384b 100644 --- a/src/tools/clippy/tests/ui/let_if_seq.stderr +++ b/src/tools/clippy/tests/ui/let_if_seq.stderr @@ -52,5 +52,29 @@ LL | | } | = note: you might not need `mut` at all -error: aborting due to 4 previous errors +error: `if _ { .. } else { .. }` is an expression + --> tests/ui/let_if_seq.rs:144:5 + | +LL | / let foo; +LL | | +LL | | if bar() { +LL | | foo = 42; +LL | | } else { +LL | | foo = 0; +LL | | } + | |_____^ help: it is more idiomatic to write: `let foo = if bar() { 42 } else { 0 };` + +error: `if _ { .. } else { .. }` is an expression + --> tests/ui/let_if_seq.rs:163:5 + | +LL | / let foo; +LL | | +LL | | if mac!(bar) { +LL | | foo = mac!(10, 4); +LL | | } else { +LL | | foo = 0; +LL | | } + | |_____^ help: it is more idiomatic to write: `let foo = if mac!(bar) { mac!(10, 4) } else { 0 };` + +error: aborting due to 6 previous errors diff --git a/src/tools/clippy/tests/ui/match_single_binding.fixed b/src/tools/clippy/tests/ui/match_single_binding.fixed index 7e899a476666..fa82a316d64d 100644 --- a/src/tools/clippy/tests/ui/match_single_binding.fixed +++ b/src/tools/clippy/tests/ui/match_single_binding.fixed @@ -265,3 +265,82 @@ fn issue15269(a: usize, b: usize, c: usize) -> bool { a < b && b < c } + +#[allow( + irrefutable_let_patterns, + clippy::blocks_in_conditions, + clippy::unused_unit, + clippy::let_unit_value, + clippy::unit_arg, + clippy::unnecessary_operation +)] +fn issue15537(a: i32) -> ((), (), ()) { + let y = ( + { todo!() }, + { + { a }; + () + }, + (), + ); + + let y = [ + { todo!() }, + { + { a }; + () + }, + (), + ]; + + fn call(x: (), y: (), z: ()) {} + let y = call( + { todo!() }, + { + { a }; + () + }, + (), + ); + + struct Foo; + impl Foo { + fn method(&self, x: (), y: (), z: ()) {} + } + let x = Foo; + x.method( + { todo!() }, + { + { a }; + () + }, + (), + ); + + -{ + { a }; + 1 + }; + + _ = { a }; + 1; + + if let x = { + { a }; + 1 + } {} + + if { + { a }; + true + } { + todo!() + } + + [1, 2, 3][{ + { a }; + 1usize + }]; + + todo!() +} diff --git a/src/tools/clippy/tests/ui/match_single_binding.rs b/src/tools/clippy/tests/ui/match_single_binding.rs index 37a96f2287c8..6c1fae89e230 100644 --- a/src/tools/clippy/tests/ui/match_single_binding.rs +++ b/src/tools/clippy/tests/ui/match_single_binding.rs @@ -342,3 +342,84 @@ fn issue15269(a: usize, b: usize, c: usize) -> bool { (a, b) => b < c, } } + +#[allow( + irrefutable_let_patterns, + clippy::blocks_in_conditions, + clippy::unused_unit, + clippy::let_unit_value, + clippy::unit_arg, + clippy::unnecessary_operation +)] +fn issue15537(a: i32) -> ((), (), ()) { + let y = ( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + let y = [ + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ]; + + fn call(x: (), y: (), z: ()) {} + let y = call( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + struct Foo; + impl Foo { + fn method(&self, x: (), y: (), z: ()) {} + } + let x = Foo; + x.method( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + -match { a } { + //~^ match_single_binding + _ => 1, + }; + + _ = match { a } { + //~^ match_single_binding + _ => 1, + }; + + if let x = match { a } { + //~^ match_single_binding + _ => 1, + } {} + + if match { a } { + //~^ match_single_binding + _ => true, + } { + todo!() + } + + [1, 2, 3][match { a } { + //~^ match_single_binding + _ => 1usize, + }]; + + todo!() +} diff --git a/src/tools/clippy/tests/ui/match_single_binding.stderr b/src/tools/clippy/tests/ui/match_single_binding.stderr index 82fc43aaa5ea..8a402dcca847 100644 --- a/src/tools/clippy/tests/ui/match_single_binding.stderr +++ b/src/tools/clippy/tests/ui/match_single_binding.stderr @@ -525,5 +525,161 @@ LL | | (a, b) => b < c, LL | | } | |_________^ help: consider using the match body instead: `b < c` -error: aborting due to 37 previous errors +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:357:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:366:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:376:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:390:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:397:6 + | +LL | -match { a } { + | ______^ +LL | | +LL | | _ => 1, +LL | | }; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ -{ +LL + { a }; +LL + 1 +LL ~ }; + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:402:9 + | +LL | _ = match { a } { + | _________^ +LL | | +LL | | _ => 1, +LL | | }; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ _ = { a }; +LL ~ 1; + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:407:16 + | +LL | if let x = match { a } { + | ________________^ +LL | | +LL | | _ => 1, +LL | | } {} + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ if let x = { +LL + { a }; +LL + 1 +LL ~ } {} + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:412:8 + | +LL | if match { a } { + | ________^ +LL | | +LL | | _ => true, +LL | | } { + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ if { +LL + { a }; +LL + true +LL ~ } { + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:419:15 + | +LL | [1, 2, 3][match { a } { + | _______________^ +LL | | +LL | | _ => 1usize, +LL | | }]; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ [1, 2, 3][{ +LL + { a }; +LL + 1usize +LL ~ }]; + | + +error: aborting due to 46 previous errors diff --git a/src/tools/clippy/tests/ui/missing_asserts_for_indexing.fixed b/src/tools/clippy/tests/ui/missing_asserts_for_indexing.fixed index 9018f38100ef..50bc576dd1e2 100644 --- a/src/tools/clippy/tests/ui/missing_asserts_for_indexing.fixed +++ b/src/tools/clippy/tests/ui/missing_asserts_for_indexing.fixed @@ -150,9 +150,9 @@ fn highest_index_first(v1: &[u8]) { } fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { - assert!(v1.len() == 3); + assert_eq!(v1.len(), 3); assert_eq!(v2.len(), 4); - assert!(v3.len() == 3); + assert_eq!(v3.len(), 3); assert_eq!(4, v4.len()); let _ = v1[0] + v1[1] + v1[2]; @@ -166,4 +166,18 @@ fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { let _ = v4[0] + v4[1] + v4[2]; } +mod issue15988 { + fn assert_eq_len(v: &[i32]) { + assert_eq!(v.len(), 3); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } + + fn debug_assert_eq_len(v: &[i32]) { + debug_assert_eq!(v.len(), 3); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/missing_asserts_for_indexing.rs b/src/tools/clippy/tests/ui/missing_asserts_for_indexing.rs index 44c5eddf3d8b..9e219a2af073 100644 --- a/src/tools/clippy/tests/ui/missing_asserts_for_indexing.rs +++ b/src/tools/clippy/tests/ui/missing_asserts_for_indexing.rs @@ -166,4 +166,18 @@ fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { let _ = v4[0] + v4[1] + v4[2]; } +mod issue15988 { + fn assert_eq_len(v: &[i32]) { + assert_eq!(v.len(), 2); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } + + fn debug_assert_eq_len(v: &[i32]) { + debug_assert_eq!(v.len(), 2); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/missing_asserts_for_indexing.stderr b/src/tools/clippy/tests/ui/missing_asserts_for_indexing.stderr index b610de94b530..b686eda7530a 100644 --- a/src/tools/clippy/tests/ui/missing_asserts_for_indexing.stderr +++ b/src/tools/clippy/tests/ui/missing_asserts_for_indexing.stderr @@ -305,7 +305,7 @@ error: indexing into a slice multiple times with an `assert` that does not cover --> tests/ui/missing_asserts_for_indexing.rs:158:13 | LL | assert_eq!(v1.len(), 2); - | ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() == 3)` + | ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v1.len(), 3)` ... LL | let _ = v1[0] + v1[1] + v1[2]; | ^^^^^^^^^^^^^^^^^^^^^ @@ -331,7 +331,7 @@ error: indexing into a slice multiple times with an `assert` that does not cover --> tests/ui/missing_asserts_for_indexing.rs:163:13 | LL | assert_eq!(2, v3.len()); - | ----------------------- help: provide the highest index that is indexed with: `assert!(v3.len() == 3)` + | ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v3.len(), 3)` ... LL | let _ = v3[0] + v3[1] + v3[2]; | ^^^^^^^^^^^^^^^^^^^^^ @@ -353,5 +353,55 @@ LL | let _ = v3[0] + v3[1] + v3[2]; | ^^^^^ = note: asserting the length before indexing will elide bounds checks -error: aborting due to 13 previous errors +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> tests/ui/missing_asserts_for_indexing.rs:172:17 + | +LL | assert_eq!(v.len(), 2); + | ---------------------- help: provide the highest index that is indexed with: `assert_eq!(v.len(), 3)` +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:17 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:24 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:31 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> tests/ui/missing_asserts_for_indexing.rs:178:17 + | +LL | debug_assert_eq!(v.len(), 2); + | ---------------------------- help: provide the highest index that is indexed with: `debug_assert_eq!(v.len(), 3)` +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:17 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:24 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:31 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: aborting due to 15 previous errors diff --git a/src/tools/clippy/tests/ui/missing_inline_executable.rs b/src/tools/clippy/tests/ui/missing_inline_executable.rs index 444a7f1c964f..2ab3e3b7825d 100644 --- a/src/tools/clippy/tests/ui/missing_inline_executable.rs +++ b/src/tools/clippy/tests/ui/missing_inline_executable.rs @@ -1,7 +1,6 @@ -//@ check-pass - #![warn(clippy::missing_inline_in_public_items)] pub fn foo() {} +//~^ missing_inline_in_public_items fn main() {} diff --git a/src/tools/clippy/tests/ui/missing_inline_executable.stderr b/src/tools/clippy/tests/ui/missing_inline_executable.stderr new file mode 100644 index 000000000000..3108e4e49065 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline_executable.stderr @@ -0,0 +1,11 @@ +error: missing `#[inline]` for a function + --> tests/ui/missing_inline_executable.rs:3:1 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-inline-in-public-items` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_inline_in_public_items)]` + +error: aborting due to 1 previous error + diff --git a/src/tools/clippy/tests/ui/missing_inline_test_crate.rs b/src/tools/clippy/tests/ui/missing_inline_test_crate.rs new file mode 100644 index 000000000000..728292a0ee2b --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline_test_crate.rs @@ -0,0 +1,10 @@ +//@compile-flags: --test +//@check-pass +#![warn(clippy::missing_inline_in_public_items)] + +#[expect(clippy::missing_inline_in_public_items)] +pub fn foo() -> u32 { + 0 +} + +fn private_function() {} diff --git a/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.rs b/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.rs index 15c8b7c50878..4b973d762ed9 100644 --- a/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.rs +++ b/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.rs @@ -13,8 +13,7 @@ impl A {} //~^ multiple_inherent_impl #[cfg(test)] -impl A {} // false positive -//~^ multiple_inherent_impl +impl A {} #[cfg(test)] impl A {} @@ -25,8 +24,7 @@ impl A {} impl B {} #[cfg(test)] -impl B {} // false positive -//~^ multiple_inherent_impl +impl B {} impl B {} //~^ multiple_inherent_impl diff --git a/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.stderr b/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.stderr index 9d408ce3dec3..991ceb0ff967 100644 --- a/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.stderr +++ b/src/tools/clippy/tests/ui/multiple_inherent_impl_cfg.stderr @@ -16,76 +16,52 @@ LL | #![deny(clippy::multiple_inherent_impl)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: multiple implementations of this structure + --> tests/ui/multiple_inherent_impl_cfg.rs:19:1 + | +LL | impl A {} + | ^^^^^^^^^ + | +note: first implementation here --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 | -LL | impl A {} // false positive - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 - | LL | impl A {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:20:1 - | -LL | impl A {} - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 - | -LL | impl A {} - | ^^^^^^^^^ - -error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 - | -LL | impl B {} // false positive - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 - | -LL | impl B {} - | ^^^^^^^^^ - -error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:31:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:29:1 | LL | impl B {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:24:1 | LL | impl B {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:35:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:33:1 | LL | impl B {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:27:1 | LL | impl B {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:45:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:43:1 | LL | impl C {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:42:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:40:1 | LL | impl C {} | ^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr index b5c0aae8310f..bb42e5ea63f5 100644 --- a/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type.stderr @@ -2,52 +2,99 @@ error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:10:16 | LL | pub fn bad(self: Self) { - | ^^^^^^^^^^ help: consider to change this parameter to: `self` + | ^^^^^^^^^^ | = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_arbitrary_self_type)]` +help: remove the type + | +LL - pub fn bad(self: Self) { +LL + pub fn bad(self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:19:20 | LL | pub fn mut_bad(mut self: Self) { - | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `mut self` + | ^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_bad(mut self: Self) { +LL + pub fn mut_bad(mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:28:20 | LL | pub fn ref_bad(self: &Self) { - | ^^^^^^^^^^^ help: consider to change this parameter to: `&self` + | ^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn ref_bad(self: &Self) { +LL + pub fn ref_bad(&self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:37:38 | LL | pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { - | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a self` + | ^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { +LL + pub fn ref_bad_with_lifetime<'a>(&'a self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:46:24 | LL | pub fn mut_ref_bad(self: &mut Self) { - | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + | ^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_ref_bad(self: &mut Self) { +LL + pub fn mut_ref_bad(&mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:55:42 | LL | pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { - | ^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a mut self` + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { +LL + pub fn mut_ref_bad_with_lifetime<'a>(&'a mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:74:11 | LL | fn f1(self: &'r#struct Self) {} - | ^^^^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'r#struct self` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - fn f1(self: &'r#struct Self) {} +LL + fn f1(&'r#struct self) {} + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:76:11 | LL | fn f2(self: &'r#struct mut Self) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'r#struct mut self` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - fn f2(self: &'r#struct mut Self) {} +LL + fn f2(&'r#struct mut self) {} + | error: aborting due to 8 previous errors diff --git a/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr b/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr index b50e00575629..4f8f001fc5e4 100644 --- a/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr +++ b/src/tools/clippy/tests/ui/needless_arbitrary_self_type_unfixable.stderr @@ -2,10 +2,15 @@ error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type_unfixable.rs:42:31 | LL | fn call_with_mut_self(self: &mut Self) {} - | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + | ^^^^^^^^^^^^^^^ | = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_arbitrary_self_type)]` +help: remove the type + | +LL - fn call_with_mut_self(self: &mut Self) {} +LL + fn call_with_mut_self(&mut self) {} + | error: aborting due to 1 previous error diff --git a/src/tools/clippy/tests/ui/needless_collect.fixed b/src/tools/clippy/tests/ui/needless_collect.fixed index 842d77dbc8c5..ba1451bf9704 100644 --- a/src/tools/clippy/tests/ui/needless_collect.fixed +++ b/src/tools/clippy/tests/ui/needless_collect.fixed @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().any(|x| x == 1); //~^ needless_collect + + let _ = sample.iter().cloned().nth(1).unwrap(); + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/src/tools/clippy/tests/ui/needless_collect.rs b/src/tools/clippy/tests/ui/needless_collect.rs index 98d8d27321d2..e054cd01e6f5 100644 --- a/src/tools/clippy/tests/ui/needless_collect.rs +++ b/src/tools/clippy/tests/ui/needless_collect.rs @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().collect::>().contains(&1); //~^ needless_collect + + let _ = sample.iter().cloned().collect::>()[1]; + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/src/tools/clippy/tests/ui/needless_collect.stderr b/src/tools/clippy/tests/ui/needless_collect.stderr index 00745eb2923c..c77674dc55d4 100644 --- a/src/tools/clippy/tests/ui/needless_collect.stderr +++ b/src/tools/clippy/tests/ui/needless_collect.stderr @@ -20,100 +20,106 @@ LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:27:35 + --> tests/ui/needless_collect.rs:24:36 + | +LL | let _ = sample.iter().cloned().collect::>()[1]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `nth(1).unwrap()` + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:31:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:29:35 + --> tests/ui/needless_collect.rs:33:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:37:19 + --> tests/ui/needless_collect.rs:41:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:39:19 + --> tests/ui/needless_collect.rs:43:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:41:28 + --> tests/ui/needless_collect.rs:45:28 | LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:43:19 + --> tests/ui/needless_collect.rs:47:19 | LL | sample.iter().collect::>().contains(&&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:47:19 + --> tests/ui/needless_collect.rs:51:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:49:19 + --> tests/ui/needless_collect.rs:53:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:55:27 + --> tests/ui/needless_collect.rs:59:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:57:27 + --> tests/ui/needless_collect.rs:61:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:80:27 + --> tests/ui/needless_collect.rs:84:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:82:27 + --> tests/ui/needless_collect.rs:86:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:87:40 + --> tests/ui/needless_collect.rs:91:40 | LL | Vec::::new().extend((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:89:20 + --> tests/ui/needless_collect.rs:93:20 | LL | foo((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:91:49 + --> tests/ui/needless_collect.rs:95:49 | LL | bar((0..10).collect::>(), (0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:93:37 + --> tests/ui/needless_collect.rs:97:37 | LL | baz((0..10), (), ('a'..='z').collect::>()) | ^^^^^^^^^^^^^^^^^^^^ help: remove this call -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.fixed b/src/tools/clippy/tests/ui/nonminimal_bool_methods.fixed index f50af147c60c..0de944f9edcf 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool_methods.fixed +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.fixed @@ -242,4 +242,9 @@ fn issue_13436() { } } +fn issue16014() { + (vec![1, 2, 3] > vec![1, 2, 3, 3]); + //~^ nonminimal_bool +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs b/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs index 0ecd4775035b..ac0bd6d8a491 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs @@ -242,4 +242,9 @@ fn before_stabilization() { } } +fn issue16014() { + !(vec![1, 2, 3] <= vec![1, 2, 3, 3]); + //~^ nonminimal_bool +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr b/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr index b5155b3b1696..568e88007727 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr @@ -247,5 +247,11 @@ error: this boolean expression can be simplified LL | _ = !opt.is_none_or(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_ok())` -error: aborting due to 41 previous errors +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:246:5 + | +LL | !(vec![1, 2, 3] <= vec![1, 2, 3, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(vec![1, 2, 3] > vec![1, 2, 3, 3])` + +error: aborting due to 42 previous errors diff --git a/src/tools/clippy/tests/ui/ok_expect.fixed b/src/tools/clippy/tests/ui/ok_expect.fixed new file mode 100644 index 000000000000..2a05b8805e4d --- /dev/null +++ b/src/tools/clippy/tests/ui/ok_expect.fixed @@ -0,0 +1,51 @@ +#![allow(clippy::unnecessary_literal_unwrap)] + +use std::io; + +struct MyError(()); // doesn't implement Debug + +#[derive(Debug)] +struct MyErrorWithParam { + x: T, +} + +fn main() { + let res: Result = Ok(0); + let _ = res.unwrap(); + + res.expect("disaster!"); + //~^ ok_expect + + res.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^ ok_expect + + let resres = res; + resres.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^^ ok_expect + + // this one has a suboptimal suggestion, but oh well + std::process::Command::new("rustc") + .arg("-vV") + .output().expect("failed to get rustc version"); + //~^^^^^ ok_expect + + // the following should not warn, since `expect` isn't implemented unless + // the error type implements `Debug` + let res2: Result = Ok(0); + res2.ok().expect("oh noes!"); + let res3: Result> = Ok(0); + res3.expect("whoof"); + //~^ ok_expect + + let res4: Result = Ok(0); + res4.expect("argh"); + //~^ ok_expect + + let res5: io::Result = Ok(0); + res5.expect("oops"); + //~^ ok_expect + + let res6: Result = Ok(0); + res6.expect("meh"); + //~^ ok_expect +} diff --git a/src/tools/clippy/tests/ui/ok_expect.rs b/src/tools/clippy/tests/ui/ok_expect.rs index efb56f242a74..3761aa26f6e8 100644 --- a/src/tools/clippy/tests/ui/ok_expect.rs +++ b/src/tools/clippy/tests/ui/ok_expect.rs @@ -16,6 +16,24 @@ fn main() { res.ok().expect("disaster!"); //~^ ok_expect + res.ok() + .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^ ok_expect + + let resres = res; + resres + .ok() + .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^^ ok_expect + + // this one has a suboptimal suggestion, but oh well + std::process::Command::new("rustc") + .arg("-vV") + .output() + .ok() + .expect("failed to get rustc version"); + //~^^^^^ ok_expect + // the following should not warn, since `expect` isn't implemented unless // the error type implements `Debug` let res2: Result = Ok(0); diff --git a/src/tools/clippy/tests/ui/ok_expect.stderr b/src/tools/clippy/tests/ui/ok_expect.stderr index a9e3533d8ca1..848a10e671db 100644 --- a/src/tools/clippy/tests/ui/ok_expect.stderr +++ b/src/tools/clippy/tests/ui/ok_expect.stderr @@ -4,41 +4,109 @@ error: called `ok().expect()` on a `Result` value LL | res.ok().expect("disaster!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` = note: `-D clippy::ok-expect` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::ok_expect)]` +help: call `expect()` directly on the `Result` + | +LL - res.ok().expect("disaster!"); +LL + res.expect("disaster!"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:19:5 + | +LL | / res.ok() +LL | | .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | |___________________________________________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - res.ok() +LL - .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); +LL + res.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | error: called `ok().expect()` on a `Result` value --> tests/ui/ok_expect.rs:24:5 | +LL | / resres +LL | | .ok() +LL | | .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | |___________________________________________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - resres +LL - .ok() +LL - .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); +LL + resres.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:30:5 + | +LL | / std::process::Command::new("rustc") +LL | | .arg("-vV") +LL | | .output() +LL | | .ok() +LL | | .expect("failed to get rustc version"); + | |______________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - .output() +LL - .ok() +LL - .expect("failed to get rustc version"); +LL + .output().expect("failed to get rustc version"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:42:5 + | LL | res3.ok().expect("whoof"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res3.ok().expect("whoof"); +LL + res3.expect("whoof"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:28:5 + --> tests/ui/ok_expect.rs:46:5 | LL | res4.ok().expect("argh"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res4.ok().expect("argh"); +LL + res4.expect("argh"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:32:5 + --> tests/ui/ok_expect.rs:50:5 | LL | res5.ok().expect("oops"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res5.ok().expect("oops"); +LL + res5.expect("oops"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:36:5 + --> tests/ui/ok_expect.rs:54:5 | LL | res6.ok().expect("meh"); | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res6.ok().expect("meh"); +LL + res6.expect("meh"); + | -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors diff --git a/src/tools/clippy/tests/ui/rc_buffer.fixed b/src/tools/clippy/tests/ui/rc_buffer.fixed index c71a4072b962..a41f98c8fa35 100644 --- a/src/tools/clippy/tests/ui/rc_buffer.fixed +++ b/src/tools/clippy/tests/ui/rc_buffer.fixed @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::cell::RefCell; use std::ffi::OsString; @@ -32,4 +31,9 @@ fn func_bad4(_: Rc) {} // does not trigger lint fn func_good1(_: Rc>) {} +mod issue_15802 { + fn foo(_: std::rc::Rc<[u8]>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer.rs b/src/tools/clippy/tests/ui/rc_buffer.rs index 686c2644da17..879f60647472 100644 --- a/src/tools/clippy/tests/ui/rc_buffer.rs +++ b/src/tools/clippy/tests/ui/rc_buffer.rs @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::cell::RefCell; use std::ffi::OsString; @@ -32,4 +31,9 @@ fn func_bad4(_: Rc) {} // does not trigger lint fn func_good1(_: Rc>) {} +mod issue_15802 { + fn foo(_: std::rc::Rc>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer.stderr b/src/tools/clippy/tests/ui/rc_buffer.stderr index 7500523ab4ac..e31e9c9c8fdf 100644 --- a/src/tools/clippy/tests/ui/rc_buffer.stderr +++ b/src/tools/clippy/tests/ui/rc_buffer.stderr @@ -1,53 +1,112 @@ -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:11:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:10:11 | LL | bad1: Rc, - | ^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^ | = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` +help: try + | +LL - bad1: Rc, +LL + bad1: Rc, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:13:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:12:11 | LL | bad2: Rc, - | ^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^ + | +help: try + | +LL - bad2: Rc, +LL + bad2: Rc, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:15:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:14:11 | LL | bad3: Rc>, - | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + | ^^^^^^^^^^^ + | +help: try + | +LL - bad3: Rc>, +LL + bad3: Rc<[u8]>, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:17:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:16:11 | LL | bad4: Rc, - | ^^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad4: Rc, +LL + bad4: Rc, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:24:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:23:17 | LL | fn func_bad1(_: Rc) {} - | ^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^ + | +help: try + | +LL - fn func_bad1(_: Rc) {} +LL + fn func_bad1(_: Rc) {} + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:26:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:25:17 | LL | fn func_bad2(_: Rc) {} - | ^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad2(_: Rc) {} +LL + fn func_bad2(_: Rc) {} + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:28:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:27:17 | LL | fn func_bad3(_: Rc>) {} - | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad3(_: Rc>) {} +LL + fn func_bad3(_: Rc<[u8]>) {} + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:30:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:29:17 | LL | fn func_bad4(_: Rc) {} - | ^^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad4(_: Rc) {} +LL + fn func_bad4(_: Rc) {} + | -error: aborting due to 8 previous errors +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:35:15 + | +LL | fn foo(_: std::rc::Rc>) {} + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - fn foo(_: std::rc::Rc>) {} +LL + fn foo(_: std::rc::Rc<[u8]>) {} + | + +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/rc_buffer_arc.fixed b/src/tools/clippy/tests/ui/rc_buffer_arc.fixed index 27059e3f2e1f..36b989ec1b60 100644 --- a/src/tools/clippy/tests/ui/rc_buffer_arc.fixed +++ b/src/tools/clippy/tests/ui/rc_buffer_arc.fixed @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::ffi::OsString; use std::path::PathBuf; @@ -31,4 +30,9 @@ fn func_bad4(_: Arc) {} // does not trigger lint fn func_good1(_: Arc>) {} +mod issue_15802 { + fn foo(_: std::sync::Arc<[u8]>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer_arc.rs b/src/tools/clippy/tests/ui/rc_buffer_arc.rs index 5261eae2f26a..f8e78dc5c18f 100644 --- a/src/tools/clippy/tests/ui/rc_buffer_arc.rs +++ b/src/tools/clippy/tests/ui/rc_buffer_arc.rs @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::ffi::OsString; use std::path::PathBuf; @@ -31,4 +30,9 @@ fn func_bad4(_: Arc) {} // does not trigger lint fn func_good1(_: Arc>) {} +mod issue_15802 { + fn foo(_: std::sync::Arc>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/rc_buffer_arc.stderr b/src/tools/clippy/tests/ui/rc_buffer_arc.stderr index 786715463232..043f7a15ec00 100644 --- a/src/tools/clippy/tests/ui/rc_buffer_arc.stderr +++ b/src/tools/clippy/tests/ui/rc_buffer_arc.stderr @@ -1,53 +1,112 @@ -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:10:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:9:11 | LL | bad1: Arc, - | ^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^ | = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` +help: try + | +LL - bad1: Arc, +LL + bad1: Arc, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:12:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:11:11 | LL | bad2: Arc, - | ^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad2: Arc, +LL + bad2: Arc, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:14:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:13:11 | LL | bad3: Arc>, - | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad3: Arc>, +LL + bad3: Arc<[u8]>, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:16:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:15:11 | LL | bad4: Arc, - | ^^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^^ + | +help: try + | +LL - bad4: Arc, +LL + bad4: Arc, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:23:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:22:17 | LL | fn func_bad1(_: Arc) {} - | ^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad1(_: Arc) {} +LL + fn func_bad1(_: Arc) {} + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:25:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:24:17 | LL | fn func_bad2(_: Arc) {} - | ^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad2(_: Arc) {} +LL + fn func_bad2(_: Arc) {} + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:27:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:26:17 | LL | fn func_bad3(_: Arc>) {} - | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad3(_: Arc>) {} +LL + fn func_bad3(_: Arc<[u8]>) {} + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:29:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:28:17 | LL | fn func_bad4(_: Arc) {} - | ^^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad4(_: Arc) {} +LL + fn func_bad4(_: Arc) {} + | -error: aborting due to 8 previous errors +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:34:15 + | +LL | fn foo(_: std::sync::Arc>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - fn foo(_: std::sync::Arc>) {} +LL + fn foo(_: std::sync::Arc<[u8]>) {} + | + +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/replace_box.fixed b/src/tools/clippy/tests/ui/replace_box.fixed index 58c8ed1691d7..e3fc7190a9c9 100644 --- a/src/tools/clippy/tests/ui/replace_box.fixed +++ b/src/tools/clippy/tests/ui/replace_box.fixed @@ -70,3 +70,74 @@ fn main() { let bb: Box; bb = Default::default(); } + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + *x = Foo { inner: String::new() }; + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + *x = Foo { inner: String::new() }; + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| **x = String::new(); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + *s.b = T::new(); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + *s.b = T::new(); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + *q.0 = T::new(); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/src/tools/clippy/tests/ui/replace_box.rs b/src/tools/clippy/tests/ui/replace_box.rs index e1fb223e4f21..1d5ca1b24994 100644 --- a/src/tools/clippy/tests/ui/replace_box.rs +++ b/src/tools/clippy/tests/ui/replace_box.rs @@ -70,3 +70,74 @@ fn main() { let bb: Box; bb = Default::default(); } + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| *x = Box::new(String::new()); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + s.b = Box::new(T::new()); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + s.b = Box::new(T::new()); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + q.0 = Box::new(T::new()); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/src/tools/clippy/tests/ui/replace_box.stderr b/src/tools/clippy/tests/ui/replace_box.stderr index 7d9c85da1731..4b7bd4a0eeae 100644 --- a/src/tools/clippy/tests/ui/replace_box.stderr +++ b/src/tools/clippy/tests/ui/replace_box.stderr @@ -48,5 +48,53 @@ LL | b = Box::new(mac!(three)); | = note: this creates a needless allocation -error: aborting due to 6 previous errors +error: creating a new box + --> tests/ui/replace_box.rs:86:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:92:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:97:38 + | +LL | static R: fn(&mut Box) = |x| *x = Box::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `**x = String::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:122:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:126:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:136:5 + | +LL | q.0 = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*q.0 = T::new()` + | + = note: this creates a needless allocation + +error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/single_range_in_vec_init.1.fixed b/src/tools/clippy/tests/ui/single_range_in_vec_init.1.fixed new file mode 100644 index 000000000000..0af91907ad05 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_range_in_vec_init.1.fixed @@ -0,0 +1,84 @@ +//@aux-build:proc_macros.rs +#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] +#![warn(clippy::single_range_in_vec_init)] + +#[macro_use] +extern crate proc_macros; + +macro_rules! a { + () => { + vec![0..200]; + }; +} + +fn awa(start: T, end: T) { + [start..end]; +} + +fn awa_vec(start: T, end: T) { + vec![start..end]; +} + +fn main() { + // Lint + (0..200).collect::>(); + //~^ single_range_in_vec_init + (0..200).collect::>(); + //~^ single_range_in_vec_init + (0u8..200).collect::>(); + //~^ single_range_in_vec_init + (0usize..200).collect::>(); + //~^ single_range_in_vec_init + (0..200usize).collect::>(); + //~^ single_range_in_vec_init + (0u8..200).collect::>(); + //~^ single_range_in_vec_init + (0usize..200).collect::>(); + //~^ single_range_in_vec_init + (0..200usize).collect::>(); + //~^ single_range_in_vec_init + // Only suggest collect + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + // Do not lint + [0..200, 0..100]; + vec![0..200, 0..100]; + [0.0..200.0]; + vec![0.0..200.0]; + // `Copy` is not implemented for `Range`, so this doesn't matter + // FIXME: [0..200; 2]; + // FIXME: [vec!0..200; 2]; + + // Unfortunately skips any macros + a!(); + + // Skip external macros and procedural macros + external! { + [0..200]; + vec![0..200]; + } + with_span! { + span + [0..200]; + vec![0..200]; + } +} + +fn issue16042() { + use std::ops::Range; + + let input = vec![Range { start: 0, end: 5 }]; +} + +fn issue16044() { + macro_rules! as_i32 { + ($x:expr) => { + $x as i32 + }; + } + + let input = (0..as_i32!(10)).collect::>(); + //~^ single_range_in_vec_init +} diff --git a/src/tools/clippy/tests/ui/single_range_in_vec_init.2.fixed b/src/tools/clippy/tests/ui/single_range_in_vec_init.2.fixed new file mode 100644 index 000000000000..fd6b91360aeb --- /dev/null +++ b/src/tools/clippy/tests/ui/single_range_in_vec_init.2.fixed @@ -0,0 +1,84 @@ +//@aux-build:proc_macros.rs +#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] +#![warn(clippy::single_range_in_vec_init)] + +#[macro_use] +extern crate proc_macros; + +macro_rules! a { + () => { + vec![0..200]; + }; +} + +fn awa(start: T, end: T) { + [start..end]; +} + +fn awa_vec(start: T, end: T) { + vec![start..end]; +} + +fn main() { + // Lint + [0; 200]; + //~^ single_range_in_vec_init + vec![0; 200]; + //~^ single_range_in_vec_init + [0u8; 200]; + //~^ single_range_in_vec_init + [0usize; 200]; + //~^ single_range_in_vec_init + [0; 200usize]; + //~^ single_range_in_vec_init + vec![0u8; 200]; + //~^ single_range_in_vec_init + vec![0usize; 200]; + //~^ single_range_in_vec_init + vec![0; 200usize]; + //~^ single_range_in_vec_init + // Only suggest collect + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + // Do not lint + [0..200, 0..100]; + vec![0..200, 0..100]; + [0.0..200.0]; + vec![0.0..200.0]; + // `Copy` is not implemented for `Range`, so this doesn't matter + // FIXME: [0..200; 2]; + // FIXME: [vec!0..200; 2]; + + // Unfortunately skips any macros + a!(); + + // Skip external macros and procedural macros + external! { + [0..200]; + vec![0..200]; + } + with_span! { + span + [0..200]; + vec![0..200]; + } +} + +fn issue16042() { + use std::ops::Range; + + let input = vec![Range { start: 0, end: 5 }]; +} + +fn issue16044() { + macro_rules! as_i32 { + ($x:expr) => { + $x as i32 + }; + } + + let input = (0..as_i32!(10)).collect::>(); + //~^ single_range_in_vec_init +} diff --git a/src/tools/clippy/tests/ui/single_range_in_vec_init.rs b/src/tools/clippy/tests/ui/single_range_in_vec_init.rs index 0888019e101c..1cc2b894c034 100644 --- a/src/tools/clippy/tests/ui/single_range_in_vec_init.rs +++ b/src/tools/clippy/tests/ui/single_range_in_vec_init.rs @@ -1,5 +1,4 @@ //@aux-build:proc_macros.rs -//@no-rustfix: overlapping suggestions #![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] #![warn(clippy::single_range_in_vec_init)] @@ -66,3 +65,20 @@ fn main() { vec![0..200]; } } + +fn issue16042() { + use std::ops::Range; + + let input = vec![Range { start: 0, end: 5 }]; +} + +fn issue16044() { + macro_rules! as_i32 { + ($x:expr) => { + $x as i32 + }; + } + + let input = vec![0..as_i32!(10)]; + //~^ single_range_in_vec_init +} diff --git a/src/tools/clippy/tests/ui/single_range_in_vec_init.stderr b/src/tools/clippy/tests/ui/single_range_in_vec_init.stderr index b21338e38a3c..d93379777d39 100644 --- a/src/tools/clippy/tests/ui/single_range_in_vec_init.stderr +++ b/src/tools/clippy/tests/ui/single_range_in_vec_init.stderr @@ -1,5 +1,5 @@ error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:25:5 + --> tests/ui/single_range_in_vec_init.rs:24:5 | LL | [0..200]; | ^^^^^^^^ @@ -18,7 +18,7 @@ LL + [0; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:27:5 + --> tests/ui/single_range_in_vec_init.rs:26:5 | LL | vec![0..200]; | ^^^^^^^^^^^^ @@ -35,7 +35,7 @@ LL + vec![0; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:29:5 + --> tests/ui/single_range_in_vec_init.rs:28:5 | LL | [0u8..200]; | ^^^^^^^^^^ @@ -52,7 +52,7 @@ LL + [0u8; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:31:5 + --> tests/ui/single_range_in_vec_init.rs:30:5 | LL | [0usize..200]; | ^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL + [0usize; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:33:5 + --> tests/ui/single_range_in_vec_init.rs:32:5 | LL | [0..200usize]; | ^^^^^^^^^^^^^ @@ -86,7 +86,7 @@ LL + [0; 200usize]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:35:5 + --> tests/ui/single_range_in_vec_init.rs:34:5 | LL | vec![0u8..200]; | ^^^^^^^^^^^^^^ @@ -103,7 +103,7 @@ LL + vec![0u8; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:37:5 + --> tests/ui/single_range_in_vec_init.rs:36:5 | LL | vec![0usize..200]; | ^^^^^^^^^^^^^^^^^ @@ -120,7 +120,7 @@ LL + vec![0usize; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:39:5 + --> tests/ui/single_range_in_vec_init.rs:38:5 | LL | vec![0..200usize]; | ^^^^^^^^^^^^^^^^^ @@ -137,7 +137,7 @@ LL + vec![0; 200usize]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:42:5 + --> tests/ui/single_range_in_vec_init.rs:41:5 | LL | [0..200isize]; | ^^^^^^^^^^^^^ @@ -149,7 +149,7 @@ LL + (0..200isize).collect::>(); | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:44:5 + --> tests/ui/single_range_in_vec_init.rs:43:5 | LL | vec![0..200isize]; | ^^^^^^^^^^^^^^^^^ @@ -160,5 +160,17 @@ LL - vec![0..200isize]; LL + (0..200isize).collect::>(); | -error: aborting due to 10 previous errors +error: a `Vec` of `Range` that is only one element + --> tests/ui/single_range_in_vec_init.rs:82:17 + | +LL | let input = vec![0..as_i32!(10)]; + | ^^^^^^^^^^^^^^^^^^^^ + | +help: if you wanted a `Vec` that contains the entire range, try + | +LL - let input = vec![0..as_i32!(10)]; +LL + let input = (0..as_i32!(10)).collect::>(); + | + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_map_on_constructor.stderr b/src/tools/clippy/tests/ui/unnecessary_map_on_constructor.stderr index f29bfec60f72..a19116820808 100644 --- a/src/tools/clippy/tests/ui/unnecessary_map_on_constructor.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_map_on_constructor.stderr @@ -1,4 +1,4 @@ -error: unnecessary map on constructor Some(_) +error: unnecessary `map` on constructor `Some(_)` --> tests/ui/unnecessary_map_on_constructor.rs:32:13 | LL | let a = Some(x).map(fun); @@ -7,43 +7,43 @@ LL | let a = Some(x).map(fun); = note: `-D clippy::unnecessary-map-on-constructor` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_map_on_constructor)]` -error: unnecessary map on constructor Ok(_) +error: unnecessary `map` on constructor `Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:34:27 | LL | let b: SimpleResult = Ok(x).map(fun); | ^^^^^^^^^^^^^^ help: try: `Ok(fun(x))` -error: unnecessary map_err on constructor Err(_) +error: unnecessary `map_err` on constructor `Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:36:27 | LL | let c: SimpleResult = Err(err).map_err(notfun); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Err(notfun(err))` -error: unnecessary map on constructor Option::Some(_) +error: unnecessary `map` on constructor `Option::Some(_)` --> tests/ui/unnecessary_map_on_constructor.rs:39:13 | LL | let a = Option::Some(x).map(fun); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Option::Some(fun(x))` -error: unnecessary map on constructor SimpleResult::Ok(_) +error: unnecessary `map` on constructor `SimpleResult::Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:41:27 | LL | let b: SimpleResult = SimpleResult::Ok(x).map(fun); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `SimpleResult::Ok(fun(x))` -error: unnecessary map_err on constructor SimpleResult::Err(_) +error: unnecessary `map_err` on constructor `SimpleResult::Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:43:27 | LL | let c: SimpleResult = SimpleResult::Err(err).map_err(notfun); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `SimpleResult::Err(notfun(err))` -error: unnecessary map on constructor Ok(_) +error: unnecessary `map` on constructor `Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:45:52 | LL | let b: std::result::Result = Ok(x).map(fun); | ^^^^^^^^^^^^^^ help: try: `Ok(fun(x))` -error: unnecessary map_err on constructor Err(_) +error: unnecessary `map_err` on constructor `Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:47:52 | LL | let c: std::result::Result = Err(err).map_err(notfun); diff --git a/src/tools/clippy/tests/ui/unnecessary_mut_passed.fixed b/src/tools/clippy/tests/ui/unnecessary_mut_passed.fixed index 63bbadb01dcb..876b61d29519 100644 --- a/src/tools/clippy/tests/ui/unnecessary_mut_passed.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_mut_passed.fixed @@ -146,7 +146,7 @@ fn main() { my_struct.takes_raw_mut(a); } -// not supported currently +// These shouldn't be linted, see https://github.com/rust-lang/rust-clippy/pull/15962#issuecomment-3503704832 fn raw_ptrs(my_struct: MyStruct) { let mut n = 42; diff --git a/src/tools/clippy/tests/ui/unnecessary_mut_passed.rs b/src/tools/clippy/tests/ui/unnecessary_mut_passed.rs index b719ca1871b2..e92368bfffeb 100644 --- a/src/tools/clippy/tests/ui/unnecessary_mut_passed.rs +++ b/src/tools/clippy/tests/ui/unnecessary_mut_passed.rs @@ -146,7 +146,7 @@ fn main() { my_struct.takes_raw_mut(a); } -// not supported currently +// These shouldn't be linted, see https://github.com/rust-lang/rust-clippy/pull/15962#issuecomment-3503704832 fn raw_ptrs(my_struct: MyStruct) { let mut n = 42; diff --git a/src/tools/clippy/tests/ui/unused_enumerate_index.stderr b/src/tools/clippy/tests/ui/unused_enumerate_index.stderr index 14d1d20a66e4..c742cc8a85ba 100644 --- a/src/tools/clippy/tests/ui/unused_enumerate_index.stderr +++ b/src/tools/clippy/tests/ui/unused_enumerate_index.stderr @@ -1,8 +1,8 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:12:19 + --> tests/ui/unused_enumerate_index.rs:12:27 | LL | for (_, x) in v.iter().enumerate() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` @@ -13,10 +13,10 @@ LL + for x in v.iter() { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:60:19 + --> tests/ui/unused_enumerate_index.rs:60:24 | LL | for (_, x) in dummy.enumerate() { - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -25,10 +25,10 @@ LL + for x in dummy { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:65:39 + --> tests/ui/unused_enumerate_index.rs:65:38 | LL | let _ = vec![1, 2, 3].into_iter().enumerate().map(|(_, x)| println!("{x}")); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -37,10 +37,10 @@ LL + let _ = vec![1, 2, 3].into_iter().map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:68:39 + --> tests/ui/unused_enumerate_index.rs:68:38 | LL | let p = vec![1, 2, 3].into_iter().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -50,10 +50,10 @@ LL ~ p.map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:90:17 + --> tests/ui/unused_enumerate_index.rs:90:16 | LL | _ = mac2!().enumerate().map(|(_, _v)| {}); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -62,10 +62,10 @@ LL + _ = mac2!().map(|_v| {}); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:99:39 + --> tests/ui/unused_enumerate_index.rs:99:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -75,10 +75,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:105:39 + --> tests/ui/unused_enumerate_index.rs:105:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -88,10 +88,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:110:39 + --> tests/ui/unused_enumerate_index.rs:110:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | diff --git a/src/tools/clippy/tests/ui/write_literal.fixed b/src/tools/clippy/tests/ui/write_literal.fixed index 29352fd468ea..ae29f3a57462 100644 --- a/src/tools/clippy/tests/ui/write_literal.fixed +++ b/src/tools/clippy/tests/ui/write_literal.fixed @@ -70,6 +70,55 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{{hello}}"); + //~^ write_literal + + writeln!(v, r"{{hello}}"); + //~^ write_literal + + writeln!(v, "'"); + //~^ write_literal + + writeln!(v, "\""); + //~^ write_literal + + writeln!(v, r"'"); + //~^ write_literal + + writeln!( + v, + "some hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some 1\ + 2 \\ 3", + //~^^^ write_literal + ); + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, r"\"); + //~^ write_literal + + writeln!(v, r#"\"#); + //~^ write_literal + + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "\""); diff --git a/src/tools/clippy/tests/ui/write_literal.rs b/src/tools/clippy/tests/ui/write_literal.rs index 928727527592..d930339e106c 100644 --- a/src/tools/clippy/tests/ui/write_literal.rs +++ b/src/tools/clippy/tests/ui/write_literal.rs @@ -70,6 +70,59 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{}", "{hello}"); + //~^ write_literal + + writeln!(v, r"{}", r"{hello}"); + //~^ write_literal + + writeln!(v, "{}", '\''); + //~^ write_literal + + writeln!(v, "{}", '"'); + //~^ write_literal + + writeln!(v, r"{}", '\''); + //~^ write_literal + + writeln!( + v, + "some {}", + "hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some {}\ + {} \\ {}", + "1", + "2", + "3", + //~^^^ write_literal + ); + writeln!(v, "{}", "\\"); + //~^ write_literal + + writeln!(v, r"{}", "\\"); + //~^ write_literal + + writeln!(v, r#"{}"#, "\\"); + //~^ write_literal + + writeln!(v, "{}", r"\"); + //~^ write_literal + + writeln!(v, "{}", "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "{}", r#"""#); diff --git a/src/tools/clippy/tests/ui/write_literal.stderr b/src/tools/clippy/tests/ui/write_literal.stderr index ca37406c8114..374098fa2b14 100644 --- a/src/tools/clippy/tests/ui/write_literal.stderr +++ b/src/tools/clippy/tests/ui/write_literal.stderr @@ -145,7 +145,156 @@ LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); | error: literal with an empty format string - --> tests/ui/write_literal.rs:75:23 + --> tests/ui/write_literal.rs:76:23 + | +LL | writeln!(v, "{}", "{hello}"); + | ^^^^^^^^^ + | +help: try + | +LL - writeln!(v, "{}", "{hello}"); +LL + writeln!(v, "{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:79:24 + | +LL | writeln!(v, r"{}", r"{hello}"); + | ^^^^^^^^^^ + | +help: try + | +LL - writeln!(v, r"{}", r"{hello}"); +LL + writeln!(v, r"{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:82:23 + | +LL | writeln!(v, "{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", '\''); +LL + writeln!(v, "'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:85:23 + | +LL | writeln!(v, "{}", '"'); + | ^^^ + | +help: try + | +LL - writeln!(v, "{}", '"'); +LL + writeln!(v, "\""); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:88:24 + | +LL | writeln!(v, r"{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", '\''); +LL + writeln!(v, r"'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:94:9 + | +LL | / "hello \ +LL | | +LL | | world!", + | |_______________^ + | +help: try + | +LL ~ "some hello \ +LL + +LL ~ world!", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:102:9 + | +LL | / "1", +LL | | "2", +LL | | "3", + | |___________^ + | +help: try + | +LL ~ "some 1\ +LL ~ 2 \\ 3", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:107:23 + | +LL | writeln!(v, "{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:110:24 + | +LL | writeln!(v, r"{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", "\\"); +LL + writeln!(v, r"\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:113:26 + | +LL | writeln!(v, r#"{}"#, "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r#"{}"#, "\\"); +LL + writeln!(v, r#"\"#); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:116: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:119:23 + | +LL | writeln!(v, "{}", "\r"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\r"); +LL + writeln!(v, "\r"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:128:23 | LL | writeln!(v, "{}", r#"""#); | ^^^^^^ @@ -157,7 +306,7 @@ LL + writeln!(v, "\""); | error: literal with an empty format string - --> tests/ui/write_literal.rs:80:9 + --> tests/ui/write_literal.rs:133:9 | LL | / r#" LL | | @@ -182,7 +331,7 @@ LL ~ " | error: literal with an empty format string - --> tests/ui/write_literal.rs:94:55 + --> tests/ui/write_literal.rs:147:55 | LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); | ^^^ @@ -194,7 +343,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:96:52 + --> tests/ui/write_literal.rs:149:52 | LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); | ^^^ @@ -206,7 +355,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:98:49 + --> tests/ui/write_literal.rs:151:49 | LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); | ^^^ @@ -218,7 +367,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:100:43 + --> tests/ui/write_literal.rs:153:43 | LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); | ^^^ @@ -229,5 +378,5 @@ LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | -error: aborting due to 18 previous errors +error: aborting due to 30 previous errors diff --git a/src/tools/clippy/tests/ui/write_literal_2.rs b/src/tools/clippy/tests/ui/write_literal_2.rs deleted file mode 100644 index f896782aaf3b..000000000000 --- a/src/tools/clippy/tests/ui/write_literal_2.rs +++ /dev/null @@ -1,65 +0,0 @@ -//@no-rustfix: overlapping suggestions -#![allow(unused_must_use)] -#![warn(clippy::write_literal)] - -use std::io::Write; - -fn main() { - let mut v = Vec::new(); - - writeln!(v, "{}", "{hello}"); - //~^ write_literal - - writeln!(v, r"{}", r"{hello}"); - //~^ write_literal - - writeln!(v, "{}", '\''); - //~^ write_literal - - writeln!(v, "{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '\''); - //~^ write_literal - - writeln!( - v, - "some {}", - "hello \ - //~^ write_literal - world!", - ); - writeln!( - v, - "some {}\ - {} \\ {}", - "1", - "2", - "3", - //~^^^ write_literal - ); - writeln!(v, "{}", "\\"); - //~^ write_literal - - writeln!(v, r"{}", "\\"); - //~^ write_literal - - writeln!(v, r#"{}"#, "\\"); - //~^ write_literal - - writeln!(v, "{}", r"\"); - //~^ write_literal - - writeln!(v, "{}", "\r"); - //~^ write_literal - - // hard mode - writeln!(v, r#"{}{}"#, '#', '"'); - //~^ write_literal - - // should not lint - writeln!(v, r"{}", "\r"); -} diff --git a/src/tools/clippy/tests/ui/write_literal_2.stderr b/src/tools/clippy/tests/ui/write_literal_2.stderr deleted file mode 100644 index 29803d6a8b18..000000000000 --- a/src/tools/clippy/tests/ui/write_literal_2.stderr +++ /dev/null @@ -1,165 +0,0 @@ -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:10:23 - | -LL | writeln!(v, "{}", "{hello}"); - | ^^^^^^^^^ - | - = note: `-D clippy::write-literal` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` -help: try - | -LL - writeln!(v, "{}", "{hello}"); -LL + writeln!(v, "{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:13:24 - | -LL | writeln!(v, r"{}", r"{hello}"); - | ^^^^^^^^^^ - | -help: try - | -LL - writeln!(v, r"{}", r"{hello}"); -LL + writeln!(v, r"{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:16:23 - | -LL | writeln!(v, "{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", '\''); -LL + writeln!(v, "'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:19:23 - | -LL | writeln!(v, "{}", '"'); - | ^^^ - | -help: try - | -LL - writeln!(v, "{}", '"'); -LL + writeln!(v, "\""); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:22:24 - | -LL | writeln!(v, r"{}", '"'); - | ^^^ - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:25:24 - | -LL | writeln!(v, r"{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", '\''); -LL + writeln!(v, r"'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:31:9 - | -LL | / "hello \ -LL | | -LL | | world!", - | |_______________^ - | -help: try - | -LL ~ "some hello \ -LL + -LL ~ world!", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:39:9 - | -LL | / "1", -LL | | "2", -LL | | "3", - | |___________^ - | -help: try - | -LL ~ "some 1\ -LL ~ 2 \\ 3", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:44:23 - | -LL | writeln!(v, "{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:47:24 - | -LL | writeln!(v, r"{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", "\\"); -LL + writeln!(v, r"\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:50:26 - | -LL | writeln!(v, r#"{}"#, "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r#"{}"#, "\\"); -LL + writeln!(v, r#"\"#); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:53: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_2.rs:56:23 - | -LL | writeln!(v, "{}", "\r"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\r"); -LL + writeln!(v, "\r"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:60:28 - | -LL | writeln!(v, r#"{}{}"#, '#', '"'); - | ^^^^^^^^ - -error: aborting due to 14 previous errors - diff --git a/src/tools/clippy/tests/ui/write_literal_unfixable.rs b/src/tools/clippy/tests/ui/write_literal_unfixable.rs new file mode 100644 index 000000000000..3a5660180779 --- /dev/null +++ b/src/tools/clippy/tests/ui/write_literal_unfixable.rs @@ -0,0 +1,16 @@ +//@no-rustfix +#![allow(unused_must_use)] +#![warn(clippy::write_literal)] + +use std::io::Write; + +fn escaping() { + let mut v = vec![]; + + writeln!(v, r"{}", '"'); + //~^ write_literal + + // hard mode + writeln!(v, r#"{}{}"#, '#', '"'); + //~^ write_literal +} diff --git a/src/tools/clippy/tests/ui/write_literal_unfixable.stderr b/src/tools/clippy/tests/ui/write_literal_unfixable.stderr new file mode 100644 index 000000000000..0dd40e891893 --- /dev/null +++ b/src/tools/clippy/tests/ui/write_literal_unfixable.stderr @@ -0,0 +1,17 @@ +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:10:24 + | +LL | writeln!(v, r"{}", '"'); + | ^^^ + | + = note: `-D clippy::write-literal` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` + +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:14:28 + | +LL | writeln!(v, r#"{}{}"#, '#', '"'); + | ^^^^^^^^ + +error: aborting due to 2 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 d34ff0a59732..e443baff0808 100644 --- a/src/tools/clippy/util/gh-pages/index_template.html +++ b/src/tools/clippy/util/gh-pages/index_template.html @@ -28,7 +28,7 @@ Otherwise, have a great day =^.^= {# #} {# #} {# #} -

{# #} +