diff options
| author | The Miri Cronjob Bot <miri@cron.bot> | 2025-09-05 05:00:55 +0000 |
|---|---|---|
| committer | The Miri Cronjob Bot <miri@cron.bot> | 2025-09-05 05:00:55 +0000 |
| commit | 7c5fdb072e3d07674ba88153a4cac4f792c20fdc (patch) | |
| tree | 78897a875065b81c78004b1cd0f83e78254fb11d /src | |
| parent | 9a1eb85394e3612213e829ed871eb281079a2364 (diff) | |
| parent | b6f8824315367f73e43efe08f6e5a88cc3f117ce (diff) | |
| download | rust-7c5fdb072e3d07674ba88153a4cac4f792c20fdc.tar.gz rust-7c5fdb072e3d07674ba88153a4cac4f792c20fdc.zip | |
Merge ref 'b3cfb8faf84c' from rust-lang/rust
Pull recent changes from https://github.com/rust-lang/rust via Josh. Upstream ref: b3cfb8faf84c5f3b7909479a9f9b6a3290d5f4d7 Filtered ref: 8995aa7743caf019203bc853f27af6006705ae30 Upstream diff: https://github.com/rust-lang/rust/compare/9385c64c95d971329e62917adc4349c8ccdbafe0...b3cfb8faf84c5f3b7909479a9f9b6a3290d5f4d7 This merge was created using https://github.com/rust-lang/josh-sync.
Diffstat (limited to 'src')
252 files changed, 5859 insertions, 2114 deletions
diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index 1535d087033..fe832652d25 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -269,14 +269,14 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -322,11 +322,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -364,12 +364,12 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "junction" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72bbdfd737a243da3dfc1f99ee8d6e166480f17ab4ac84d7c34aacd73fc7bd16" +checksum = "c52f6e1bf39a7894f618c9d378904a11dbd7e10fe3ec20d1173600e79b1408d8" dependencies = [ "scopeguard", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -434,6 +434,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] name = "ntapi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -497,12 +506,13 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opener" -version = "0.5.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c15678e37254c15bd2f092314abb4e51d7fdde05c2021279c12631b54f005" +checksum = "771b9704f8cd8b424ec747a320b30b47517a6966ba2c7da90047c16f4a962223" dependencies = [ "bstr", - "winapi", + "normpath", + "windows-sys 0.59.0", ] [[package]] @@ -757,15 +767,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -929,11 +939,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1045,20 +1055,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.2", ] [[package]] @@ -1067,14 +1077,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1084,48 +1110,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index c7175584465..9a76a7dda2a 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -42,7 +42,7 @@ home = "0.5" ignore = "0.4" libc = "0.2" object = { version = "0.36.3", default-features = false, features = ["archive", "coff", "read_core", "std", "unaligned"] } -opener = "0.5" +opener = "0.8" semver = "1.0" serde = "1.0" # Directly use serde_derive rather than through the derive feature of serde to allow building both @@ -67,7 +67,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = ["env-filter tempfile = { version = "3.15.0", optional = true } [target.'cfg(windows)'.dependencies.junction] -version = "1.0.0" +version = "1.3.0" [target.'cfg(windows)'.dependencies.windows] version = "0.61" diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index a604e7c0585..49d12b64da5 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -389,7 +389,7 @@ impl Step for Rustc { /// Represents a compiler that can check something. /// -/// If the compiler was created for `Mode::ToolRustc` or `Mode::Codegen`, it will also contain +/// If the compiler was created for `Mode::ToolRustcPrivate` or `Mode::Codegen`, it will also contain /// .rmeta artifacts from rustc that was already checked using `build_compiler`. /// /// All steps that use this struct in a "general way" (i.e. they don't know exactly what kind of @@ -469,7 +469,7 @@ pub fn prepare_compiler_for_check( build_compiler } } - Mode::ToolRustc | Mode::Codegen => { + Mode::ToolRustcPrivate | Mode::Codegen => { // Check Rustc to produce the required rmeta artifacts for rustc_private, and then // return the build compiler that was used to check rustc. // We do not need to check examples/tests/etc. of Rustc for rustc_private, so we pass @@ -767,19 +767,22 @@ fn run_tool_check_step( tool_check_step!(Rustdoc { path: "src/tools/rustdoc", alt_path: "src/librustdoc", - mode: |_builder| Mode::ToolRustc + mode: |_builder| Mode::ToolRustcPrivate }); // Clippy, miri and Rustfmt are hybrids. They are external tools, but use a git subtree instead // of a submodule. Since the SourceType only drives the deny-warnings // behavior, treat it as in-tree so that any new warnings in clippy will be // rejected. -tool_check_step!(Clippy { path: "src/tools/clippy", mode: |_builder| Mode::ToolRustc }); -tool_check_step!(Miri { path: "src/tools/miri", mode: |_builder| Mode::ToolRustc }); -tool_check_step!(CargoMiri { path: "src/tools/miri/cargo-miri", mode: |_builder| Mode::ToolRustc }); -tool_check_step!(Rustfmt { path: "src/tools/rustfmt", mode: |_builder| Mode::ToolRustc }); +tool_check_step!(Clippy { path: "src/tools/clippy", mode: |_builder| Mode::ToolRustcPrivate }); +tool_check_step!(Miri { path: "src/tools/miri", mode: |_builder| Mode::ToolRustcPrivate }); +tool_check_step!(CargoMiri { + path: "src/tools/miri/cargo-miri", + mode: |_builder| Mode::ToolRustcPrivate +}); +tool_check_step!(Rustfmt { path: "src/tools/rustfmt", mode: |_builder| Mode::ToolRustcPrivate }); tool_check_step!(RustAnalyzer { path: "src/tools/rust-analyzer", - mode: |_builder| Mode::ToolRustc, + mode: |_builder| Mode::ToolRustcPrivate, allow_features: tool::RustAnalyzer::ALLOW_FEATURES, enable_features: ["in-rust-tree"], }); diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs index 05f8b240291..2083c675e1f 100644 --- a/src/bootstrap/src/core/build_steps/clippy.rs +++ b/src/bootstrap/src/core/build_steps/clippy.rs @@ -366,8 +366,13 @@ impl Step for CodegenGcc { ); self.build_compiler.configure_cargo(&mut cargo); - let _guard = - builder.msg(Kind::Clippy, "rustc_codegen_gcc", Mode::ToolRustc, build_compiler, target); + let _guard = builder.msg( + Kind::Clippy, + "rustc_codegen_gcc", + Mode::ToolRustcPrivate, + build_compiler, + target, + ); let stamp = BuildStamp::new(&builder.cargo_out(build_compiler, Mode::Codegen, target)) .with_prefix("rustc_codegen_gcc-check"); @@ -478,8 +483,8 @@ lint_any!( Bootstrap, "src/bootstrap", "bootstrap", Mode::ToolTarget; BuildHelper, "src/build_helper", "build_helper", Mode::ToolTarget; BuildManifest, "src/tools/build-manifest", "build-manifest", Mode::ToolTarget; - CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", Mode::ToolRustc; - Clippy, "src/tools/clippy", "clippy", Mode::ToolRustc; + CargoMiri, "src/tools/miri/cargo-miri", "cargo-miri", Mode::ToolRustcPrivate; + Clippy, "src/tools/clippy", "clippy", Mode::ToolRustcPrivate; CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata", Mode::ToolTarget; Compiletest, "src/tools/compiletest", "compiletest", Mode::ToolTarget; CoverageDump, "src/tools/coverage-dump", "coverage-dump", Mode::ToolTarget; @@ -487,14 +492,14 @@ lint_any!( Jsondoclint, "src/tools/jsondoclint", "jsondoclint", Mode::ToolTarget; LintDocs, "src/tools/lint-docs", "lint-docs", Mode::ToolTarget; LlvmBitcodeLinker, "src/tools/llvm-bitcode-linker", "llvm-bitcode-linker", Mode::ToolTarget; - Miri, "src/tools/miri", "miri", Mode::ToolRustc; + Miri, "src/tools/miri", "miri", Mode::ToolRustcPrivate; MiroptTestTools, "src/tools/miropt-test-tools", "miropt-test-tools", Mode::ToolTarget; OptDist, "src/tools/opt-dist", "opt-dist", Mode::ToolTarget; RemoteTestClient, "src/tools/remote-test-client", "remote-test-client", Mode::ToolTarget; RemoteTestServer, "src/tools/remote-test-server", "remote-test-server", Mode::ToolTarget; - RustAnalyzer, "src/tools/rust-analyzer", "rust-analyzer", Mode::ToolRustc; - Rustdoc, "src/librustdoc", "clippy", Mode::ToolRustc; - Rustfmt, "src/tools/rustfmt", "rustfmt", Mode::ToolRustc; + RustAnalyzer, "src/tools/rust-analyzer", "rust-analyzer", Mode::ToolRustcPrivate; + Rustdoc, "src/librustdoc", "clippy", Mode::ToolRustcPrivate; + Rustfmt, "src/tools/rustfmt", "rustfmt", Mode::ToolRustcPrivate; RustInstaller, "src/tools/rust-installer", "rust-installer", Mode::ToolTarget; Tidy, "src/tools/tidy", "tidy", Mode::ToolTarget; TestFloatParse, "src/tools/test-float-parse", "test-float-parse", Mode::ToolStd; diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index f113dd7683d..820dda5a652 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -823,6 +823,18 @@ pub struct RustcDev { target: TargetSelection, } +impl RustcDev { + pub fn new(builder: &Builder<'_>, target: TargetSelection) -> Self { + Self { + // We currently always ship a stage 2 rustc-dev component, so we build it with the + // stage 1 compiler. This might change in the future. + // The precise stage used here is important, so we hard-code it. + build_compiler: builder.compiler(1, builder.config.host_target), + target, + } + } +} + impl Step for RustcDev { type Output = Option<GeneratedTarball>; const DEFAULT: bool = true; @@ -833,13 +845,7 @@ impl Step for RustcDev { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(RustcDev { - // We currently always ship a stage 2 rustc-dev component, so we build it with the - // stage 1 compiler. This might change in the future. - // The precise stage used here is important, so we hard-code it. - build_compiler: run.builder.compiler(1, run.builder.config.host_target), - target: run.target, - }); + run.builder.ensure(RustcDev::new(run.builder, run.target)); } fn run(self, builder: &Builder<'_>) -> Option<GeneratedTarball> { diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 0789eefa894..eb198a0051a 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -995,7 +995,7 @@ macro_rules! tool_doc { // Build rustc docs so that we generate relative links. run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target)); - (compilers.build_compiler(), Mode::ToolRustc) + (compilers.build_compiler(), Mode::ToolRustcPrivate) } else { // bootstrap/host tools have to be documented with the stage 0 compiler (prepare_doc_compiler(run.builder, run.builder.host_target, 1), Mode::ToolBootstrap) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index d47c1495838..83ed7430c39 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -205,6 +205,7 @@ pub(crate) fn is_ci_llvm_available_for_target( // tier 1 ("aarch64-unknown-linux-gnu", false), ("aarch64-apple-darwin", false), + ("aarch64-pc-windows-msvc", false), ("i686-pc-windows-gnu", false), ("i686-pc-windows-msvc", false), ("i686-unknown-linux-gnu", false), @@ -213,7 +214,6 @@ pub(crate) fn is_ci_llvm_available_for_target( ("x86_64-pc-windows-gnu", true), ("x86_64-pc-windows-msvc", true), // tier 2 with host tools - ("aarch64-pc-windows-msvc", false), ("aarch64-unknown-linux-musl", false), ("arm-unknown-linux-gnueabi", false), ("arm-unknown-linux-gnueabihf", false), diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index d9de6b7ef96..9f7248b80f7 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -161,7 +161,7 @@ impl Step for Miri { let mut miri = tool::prepare_tool_cargo( builder, compilers.build_compiler(), - Mode::ToolRustc, + Mode::ToolRustcPrivate, host, Kind::Run, "src/tools/miri", @@ -487,7 +487,7 @@ impl Step for Rustfmt { let mut rustfmt = tool::prepare_tool_cargo( builder, rustfmt_build.build_compiler, - Mode::ToolRustc, + Mode::ToolRustcPrivate, host, Kind::Run, "src/tools/rustfmt", diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index ee2cbe9385e..cb81d738666 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -455,7 +455,7 @@ impl Step for RustAnalyzer { let mut cargo = tool::prepare_tool_cargo( builder, self.compilers.build_compiler(), - Mode::ToolRustc, + Mode::ToolRustcPrivate, host, Kind::Test, crate_path, @@ -518,7 +518,7 @@ impl Step for Rustfmt { let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, - Mode::ToolRustc, + Mode::ToolRustcPrivate, target, Kind::Test, "src/tools/rustfmt", @@ -571,7 +571,8 @@ impl Miri { cargo.env("MIRI_SYSROOT", &miri_sysroot); let mut cargo = BootstrapCommand::from(cargo); - let _guard = builder.msg(Kind::Build, "miri sysroot", Mode::ToolRustc, compiler, target); + let _guard = + builder.msg(Kind::Build, "miri sysroot", Mode::ToolRustcPrivate, compiler, target); cargo.run(builder); // # Determine where Miri put its sysroot. @@ -648,7 +649,7 @@ impl Step for Miri { let mut cargo = tool::prepare_tool_cargo( builder, miri.build_compiler, - Mode::ToolRustc, + Mode::ToolRustcPrivate, host, Kind::Test, "src/tools/miri", @@ -861,7 +862,7 @@ impl Step for Clippy { let mut cargo = tool::prepare_tool_cargo( builder, build_compiler, - Mode::ToolRustc, + Mode::ToolRustcPrivate, target, Kind::Test, "src/tools/clippy", @@ -872,7 +873,7 @@ impl Step for Clippy { cargo.env("RUSTC_TEST_SUITE", builder.rustc(build_compiler)); cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(build_compiler)); let host_libs = - builder.stage_out(build_compiler, Mode::ToolRustc).join(builder.cargo_dir()); + builder.stage_out(build_compiler, Mode::ToolRustcPrivate).join(builder.cargo_dir()); cargo.env("HOST_LIBS", host_libs); // Build the standard library that the tests can use. @@ -2411,7 +2412,7 @@ impl BookTest { let libs = if !self.dependencies.is_empty() { let mut lib_paths = vec![]; for dep in self.dependencies { - let mode = Mode::ToolRustc; + let mode = Mode::ToolRustcPrivate; let target = builder.config.host_target; let cargo = tool::prepare_tool_cargo( builder, @@ -2996,7 +2997,7 @@ impl Step for CrateRustdoc { let mut cargo = tool::prepare_tool_cargo( builder, compiler, - Mode::ToolRustc, + Mode::ToolRustcPrivate, target, builder.kind, "src/tools/rustdoc", @@ -3182,6 +3183,7 @@ impl Step for Distcheck { /// check steps from those sources. /// - Check that selected dist components (`rust-src` only at the moment) at least have expected /// directory shape and crate manifests that cargo can generate a lockfile from. + /// - Check that we can run `cargo metadata` on the workspace in the `rustc-dev` component /// /// FIXME(#136822): dist components are under-tested. fn run(self, builder: &Builder<'_>) { @@ -3189,61 +3191,91 @@ impl Step for Distcheck { // local source code, built artifacts or configuration by accident let root_dir = std::env::temp_dir().join("distcheck"); - // Check that we can build some basic things from the plain source tarball - builder.info("Distcheck plain source tarball"); - let plain_src_tarball = builder.ensure(dist::PlainSourceTarball); - let plain_src_dir = root_dir.join("distcheck-plain-src"); - builder.clear_dir(&plain_src_dir); - - let configure_args: Vec<String> = std::env::var("DISTCHECK_CONFIGURE_ARGS") - .map(|args| args.split(" ").map(|s| s.to_string()).collect::<Vec<String>>()) - .unwrap_or_default(); - - command("tar") - .arg("-xf") - .arg(plain_src_tarball.tarball()) - .arg("--strip-components=1") - .current_dir(&plain_src_dir) - .run(builder); - command("./configure") - .arg("--set") - .arg("rust.omit-git-hash=false") - .args(&configure_args) - .arg("--enable-vendor") - .current_dir(&plain_src_dir) - .run(builder); - command(helpers::make(&builder.config.host_target.triple)) - .arg("check") - // Do not run the build as if we were in CI, otherwise git would be assumed to be - // present, but we build from a tarball here - .env("GITHUB_ACTIONS", "0") - .current_dir(&plain_src_dir) - .run(builder); - - // Now make sure that rust-src has all of libstd's dependencies - builder.info("Distcheck rust-src"); - let src_tarball = builder.ensure(dist::Src); - let src_dir = root_dir.join("distcheck-src"); - builder.clear_dir(&src_dir); - - command("tar") - .arg("-xf") - .arg(src_tarball.tarball()) - .arg("--strip-components=1") - .current_dir(&src_dir) - .run(builder); - - let toml = src_dir.join("rust-src/lib/rustlib/src/rust/library/std/Cargo.toml"); - command(&builder.initial_cargo) - // Will read the libstd Cargo.toml - // which uses the unstable `public-dependency` feature. - .env("RUSTC_BOOTSTRAP", "1") - .arg("generate-lockfile") - .arg("--manifest-path") - .arg(&toml) - .current_dir(&src_dir) - .run(builder); - } + distcheck_plain_source_tarball(builder, &root_dir.join("distcheck-rustc-src")); + distcheck_rust_src(builder, &root_dir.join("distcheck-rust-src")); + distcheck_rustc_dev(builder, &root_dir.join("distcheck-rustc-dev")); + } +} + +/// Check that we can build some basic things from the plain source tarball +fn distcheck_plain_source_tarball(builder: &Builder<'_>, plain_src_dir: &Path) { + builder.info("Distcheck plain source tarball"); + let plain_src_tarball = builder.ensure(dist::PlainSourceTarball); + builder.clear_dir(plain_src_dir); + + let configure_args: Vec<String> = std::env::var("DISTCHECK_CONFIGURE_ARGS") + .map(|args| args.split(" ").map(|s| s.to_string()).collect::<Vec<String>>()) + .unwrap_or_default(); + + command("tar") + .arg("-xf") + .arg(plain_src_tarball.tarball()) + .arg("--strip-components=1") + .current_dir(plain_src_dir) + .run(builder); + command("./configure") + .arg("--set") + .arg("rust.omit-git-hash=false") + .args(&configure_args) + .arg("--enable-vendor") + .current_dir(plain_src_dir) + .run(builder); + command(helpers::make(&builder.config.host_target.triple)) + .arg("check") + // Do not run the build as if we were in CI, otherwise git would be assumed to be + // present, but we build from a tarball here + .env("GITHUB_ACTIONS", "0") + .current_dir(plain_src_dir) + .run(builder); +} + +/// Check that rust-src has all of libstd's dependencies +fn distcheck_rust_src(builder: &Builder<'_>, src_dir: &Path) { + builder.info("Distcheck rust-src"); + let src_tarball = builder.ensure(dist::Src); + builder.clear_dir(src_dir); + + command("tar") + .arg("-xf") + .arg(src_tarball.tarball()) + .arg("--strip-components=1") + .current_dir(src_dir) + .run(builder); + + let toml = src_dir.join("rust-src/lib/rustlib/src/rust/library/std/Cargo.toml"); + command(&builder.initial_cargo) + // Will read the libstd Cargo.toml + // which uses the unstable `public-dependency` feature. + .env("RUSTC_BOOTSTRAP", "1") + .arg("generate-lockfile") + .arg("--manifest-path") + .arg(&toml) + .current_dir(src_dir) + .run(builder); +} + +/// Check that rustc-dev's compiler crate source code can be loaded with `cargo metadata` +fn distcheck_rustc_dev(builder: &Builder<'_>, dir: &Path) { + builder.info("Distcheck rustc-dev"); + let tarball = builder.ensure(dist::RustcDev::new(builder, builder.host_target)).unwrap(); + builder.clear_dir(dir); + + command("tar") + .arg("-xf") + .arg(tarball.tarball()) + .arg("--strip-components=1") + .current_dir(dir) + .run(builder); + + command(&builder.initial_cargo) + .arg("metadata") + .arg("--manifest-path") + .arg("rustc-dev/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml") + .env("RUSTC_BOOTSTRAP", "1") + // We might not have a globally available `rustc` binary on CI + .env("RUSTC", &builder.initial_rustc) + .current_dir(dir) + .run(builder); } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -3294,9 +3326,7 @@ impl Step for Bootstrap { .env("INSTA_WORKSPACE_ROOT", &builder.src) .env("RUSTC_BOOTSTRAP", "1"); - // bootstrap tests are racy on directory creation so just run them one at a time. - // Since there's not many this shouldn't be a problem. - run_cargo_test(cargo, &["--test-threads=1"], &[], None, host, builder); + run_cargo_test(cargo, &[], &[], None, host, builder); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index 65c4c499086..c5308034fe3 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -82,7 +82,7 @@ impl Step for ToolBuild { let path = self.path; match self.mode { - Mode::ToolRustc => { + Mode::ToolRustcPrivate => { // FIXME: remove this, it's only needed for download-rustc... if !self.build_compiler.is_forced_compiler() && builder.download_rustc() { builder.std(self.build_compiler, self.build_compiler.host); @@ -123,7 +123,7 @@ impl Step for ToolBuild { // Rustc tools (miri, clippy, cargo, rustfmt, rust-analyzer) // could use the additional optimizations. - if self.mode == Mode::ToolRustc && is_lto_stage(&self.build_compiler) { + if self.mode == Mode::ToolRustcPrivate && is_lto_stage(&self.build_compiler) { let lto = match builder.config.rust_lto { RustcLto::Off => Some("off"), RustcLto::Thin => Some("thin"), @@ -607,7 +607,7 @@ impl Step for ErrorIndex { build_compiler: self.compilers.build_compiler, target: self.compilers.target(), tool: "error_index_generator", - mode: Mode::ToolRustc, + mode: Mode::ToolRustcPrivate, path: "src/tools/error_index_generator", source_type: SourceType::InTree, extra_features: Vec::new(), @@ -671,7 +671,7 @@ impl Step for RemoteTestServer { /// Represents `Rustdoc` that either comes from the external stage0 sysroot or that is built /// locally. /// Rustdoc is special, because it both essentially corresponds to a `Compiler` (that can be -/// externally provided), but also to a `ToolRustc` tool. +/// externally provided), but also to a `ToolRustcPrivate` tool. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Rustdoc { /// If the stage of `target_compiler` is `0`, then rustdoc is externally provided. @@ -759,7 +759,7 @@ impl Step for Rustdoc { // the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool" // rustdoc a different name. tool: "rustdoc_tool_binary", - mode: Mode::ToolRustc, + mode: Mode::ToolRustcPrivate, path: "src/tools/rustdoc", source_type: SourceType::InTree, extra_features, @@ -1048,7 +1048,7 @@ impl Step for RustAnalyzer { build_compiler, target, tool: "rust-analyzer", - mode: Mode::ToolRustc, + mode: Mode::ToolRustcPrivate, path: "src/tools/rust-analyzer", extra_features: vec!["in-rust-tree".to_owned()], source_type: SourceType::InTree, @@ -1105,7 +1105,7 @@ impl Step for RustAnalyzerProcMacroSrv { build_compiler: self.compilers.build_compiler, target: self.compilers.target(), tool: "rust-analyzer-proc-macro-srv", - mode: Mode::ToolRustc, + mode: Mode::ToolRustcPrivate, path: "src/tools/rust-analyzer/crates/proc-macro-srv-cli", extra_features: vec!["in-rust-tree".to_owned()], source_type: SourceType::InTree, @@ -1352,7 +1352,7 @@ impl RustcPrivateCompilers { } } -/// Creates a step that builds an extended `Mode::ToolRustc` tool +/// Creates a step that builds an extended `Mode::ToolRustcPrivate` tool /// and installs it into the sysroot of a corresponding compiler. macro_rules! tool_rustc_extended { ( @@ -1466,7 +1466,7 @@ fn build_extended_rustc_tool( build_compiler, target, tool: tool_name, - mode: Mode::ToolRustc, + mode: Mode::ToolRustcPrivate, path, extra_features, source_type: SourceType::InTree, diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index cdf6fe573e5..cca0c803e63 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -533,7 +533,7 @@ impl Builder<'_> { if cmd_kind == Kind::Doc { let my_out = match mode { // This is the intended out directory for compiler documentation. - Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap => { + Mode::Rustc | Mode::ToolRustcPrivate | Mode::ToolBootstrap => { self.compiler_doc_out(target) } Mode::Std => { @@ -583,7 +583,7 @@ impl Builder<'_> { // We synthetically interpret a stage0 compiler used to build tools as a // "raw" compiler in that it's the exact snapshot we download. For things like - // ToolRustc, we would have to use the artificial stage0-sysroot compiler instead. + // ToolRustcPrivate, we would have to use the artificial stage0-sysroot compiler instead. let use_snapshot = mode == Mode::ToolBootstrap || (mode == Mode::ToolTarget && build_compiler_stage == 0); assert!(!use_snapshot || build_compiler_stage == 0 || self.local_rebuild); @@ -643,7 +643,8 @@ impl Builder<'_> { // sysroot. Passing this cfg enables raw-dylib support instead, which makes the native // library unnecessary. This can be removed when windows-rs enables raw-dylib // unconditionally. - if let Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap | Mode::ToolTarget = mode { + if let Mode::Rustc | Mode::ToolRustcPrivate | Mode::ToolBootstrap | Mode::ToolTarget = mode + { rustflags.arg("--cfg=windows_raw_dylib"); } @@ -657,7 +658,7 @@ impl Builder<'_> { // - rust-analyzer, due to the rowan crate // so we exclude an entire category of steps here due to lack of fine-grained control over // rustflags. - if self.config.rust_randomize_layout && mode != Mode::ToolRustc { + if self.config.rust_randomize_layout && mode != Mode::ToolRustcPrivate { rustflags.arg("-Zrandomize-layout"); } @@ -717,7 +718,7 @@ impl Builder<'_> { match mode { Mode::Std | Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolTarget => {} - Mode::Rustc | Mode::Codegen | Mode::ToolRustc => { + Mode::Rustc | Mode::Codegen | Mode::ToolRustcPrivate => { // Build proc macros both for the host and the target unless proc-macros are not // supported by the target. if target != compiler.host && cmd_kind != Kind::Check { @@ -778,7 +779,7 @@ impl Builder<'_> { "binary-dep-depinfo,proc_macro_span,proc_macro_span_shrink,proc_macro_diagnostic" .to_string() } - Mode::Std | Mode::Rustc | Mode::Codegen | Mode::ToolRustc => String::new(), + Mode::Std | Mode::Rustc | Mode::Codegen | Mode::ToolRustcPrivate => String::new(), }; cargo.arg("-j").arg(self.jobs().to_string()); @@ -825,7 +826,7 @@ impl Builder<'_> { // rustc step and one that we just built. This isn't always a // problem, somehow -- not really clear why -- but we know that this // fixes things. - Mode::ToolRustc => metadata.push_str("tool-rustc"), + Mode::ToolRustcPrivate => metadata.push_str("tool-rustc"), // Same for codegen backends. Mode::Codegen => metadata.push_str("codegen"), _ => {} @@ -877,8 +878,11 @@ impl Builder<'_> { .env("RUSTC_LIBDIR", libdir) .env("RUSTDOC", self.bootstrap_out.join("rustdoc")) .env("RUSTDOC_REAL", rustdoc_path) - .env("RUSTC_ERROR_METADATA_DST", self.extended_error_dir()) - .env("RUSTC_BREAK_ON_ICE", "1"); + .env("RUSTC_ERROR_METADATA_DST", self.extended_error_dir()); + + if self.config.rust_break_on_ice { + cargo.env("RUSTC_BREAK_ON_ICE", "1"); + } // Set RUSTC_WRAPPER to the bootstrap shim, which switches between beta and in-tree // sysroot depending on whether we're building build scripts. @@ -917,7 +921,7 @@ impl Builder<'_> { let debuginfo_level = match mode { Mode::Rustc | Mode::Codegen => self.config.rust_debuginfo_level_rustc, Mode::Std => self.config.rust_debuginfo_level_std, - Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustc | Mode::ToolTarget => { + Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustcPrivate | Mode::ToolTarget => { self.config.rust_debuginfo_level_tools } }; @@ -930,7 +934,7 @@ impl Builder<'_> { match mode { Mode::Std => self.config.std_debug_assertions, Mode::Rustc | Mode::Codegen => self.config.rustc_debug_assertions, - Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustc | Mode::ToolTarget => { + Mode::ToolBootstrap | Mode::ToolStd | Mode::ToolRustcPrivate | Mode::ToolTarget => { self.config.tools_debug_assertions } } @@ -1005,7 +1009,7 @@ impl Builder<'_> { } Mode::Std | Mode::ToolBootstrap - | Mode::ToolRustc + | Mode::ToolRustcPrivate | Mode::ToolStd | Mode::ToolTarget => { if let Some(ref map_to) = @@ -1078,7 +1082,7 @@ impl Builder<'_> { // requirement, but the `-L` library path is not propagated across // separate Cargo projects. We can add LLVM's library path to the // rustc args as a workaround. - if (mode == Mode::ToolRustc || mode == Mode::Codegen) + if (mode == Mode::ToolRustcPrivate || mode == Mode::Codegen) && let Some(llvm_config) = self.llvm_config(target) { let llvm_libdir = diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index ef01f14f5e9..9e8c13eb4de 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -10,8 +10,8 @@ use crate::core::build_steps::doc::DocumentationFormat; use crate::core::config::Config; use crate::utils::cache::ExecutedStep; use crate::utils::helpers::get_host_target; -use crate::utils::tests::ConfigBuilder; use crate::utils::tests::git::{GitCtx, git_test}; +use crate::utils::tests::{ConfigBuilder, TestCtx}; static TEST_TRIPLE_1: &str = "i686-unknown-haiku"; static TEST_TRIPLE_2: &str = "i686-unknown-hurd-gnu"; @@ -22,38 +22,13 @@ fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config { } fn configure_with_args(cmd: &[&str], host: &[&str], target: &[&str]) -> Config { - let cmd = cmd.iter().copied().map(String::from).collect::<Vec<_>>(); - let mut config = Config::parse(Flags::parse(&cmd)); - // don't save toolstates - config.save_toolstates = None; - config.set_dry_run(DryRun::SelfCheck); - - // Ignore most submodules, since we don't need them for a dry run, and the - // tests run much faster without them. - // - // The src/doc/book submodule is needed because TheBook step tries to - // access files even during a dry-run (may want to consider just skipping - // that in a dry run). - let submodule_build = Build::new(Config { - // don't include LLVM, so CI doesn't require ninja/cmake to be installed - rust_codegen_backends: vec![], - ..Config::parse(Flags::parse(&["check".to_owned()])) - }); - submodule_build.require_submodule("src/doc/book", None); - config.submodules = Some(false); - - config.ninja_in_file = false; - // try to avoid spurious failures in dist where we create/delete each others file - // HACK: rather than pull in `tempdir`, use the one that cargo has conveniently created for us - let dir = Path::new(env!("OUT_DIR")) - .join("tmp-rustbuild-tests") - .join(&thread::current().name().unwrap_or("unknown").replace(":", "-")); - t!(fs::create_dir_all(&dir)); - config.out = dir; - config.host_target = TargetSelection::from_user(TEST_TRIPLE_1); - config.hosts = host.iter().map(|s| TargetSelection::from_user(s)).collect(); - config.targets = target.iter().map(|s| TargetSelection::from_user(s)).collect(); - config + TestCtx::new() + .config(cmd[0]) + .args(&cmd[1..]) + .hosts(host) + .targets(target) + .args(&["--build", TEST_TRIPLE_1]) + .create_config() } fn first<A, B>(v: Vec<(A, B)>) -> Vec<A> { @@ -547,8 +522,8 @@ mod snapshot { use crate::core::build_steps::{compile, dist, doc, test, tool}; use crate::core::builder::tests::{ - RenderConfig, TEST_TRIPLE_1, TEST_TRIPLE_2, TEST_TRIPLE_3, configure, configure_with_args, - first, host_target, render_steps, run_build, + RenderConfig, TEST_TRIPLE_1, TEST_TRIPLE_2, TEST_TRIPLE_3, configure, first, host_target, + render_steps, run_build, }; use crate::core::builder::{Builder, Kind, StepDescription, StepMetadata}; use crate::core::config::TargetSelection; diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index d12cc962187..678a9b63952 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -221,6 +221,7 @@ pub struct Config { pub rust_lto: RustcLto, pub rust_validate_mir_opts: Option<u32>, pub rust_std_features: BTreeSet<String>, + pub rust_break_on_ice: bool, pub llvm_profile_use: Option<String>, pub llvm_profile_generate: bool, pub llvm_libunwind_default: Option<LlvmLibunwind>, @@ -550,6 +551,7 @@ impl Config { strip: rust_strip, lld_mode: rust_lld_mode, std_features: rust_std_features, + break_on_ice: rust_break_on_ice, } = toml.rust.unwrap_or_default(); let Llvm { @@ -1269,6 +1271,7 @@ impl Config { reproducible_artifacts: flags_reproducible_artifact, reuse: build_reuse.map(PathBuf::from), rust_analyzer_info, + rust_break_on_ice: rust_break_on_ice.unwrap_or(true), rust_codegen_backends: rust_codegen_backends .map(|backends| parse_codegen_backends(backends, "rust")) .unwrap_or(vec![CodegenBackendKind::Llvm]), diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index c54df456d52..4832a1d37b7 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -65,6 +65,7 @@ define_config! { lto: Option<String> = "lto", validate_mir_opts: Option<u32> = "validate-mir-opts", std_features: Option<BTreeSet<String>> = "std-features", + break_on_ice: Option<bool> = "break-on-ice", } } @@ -355,6 +356,7 @@ pub fn check_incompatible_options_for_ci_rustc( download_rustc: _, validate_mir_opts: _, frame_pointers: _, + break_on_ice: _, } = ci_rust_config; // There are two kinds of checks for CI rustc incompatible options: diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 59c0f9faaca..a2aeed20948 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -85,12 +85,12 @@ const LLD_FILE_NAMES: &[&str] = &["ld.lld", "ld64.lld", "lld-link", "wasm-ld"]; const EXTRA_CHECK_CFGS: &[(Option<Mode>, &str, Option<&[&'static str]>)] = &[ (Some(Mode::Rustc), "bootstrap", None), (Some(Mode::Codegen), "bootstrap", None), - (Some(Mode::ToolRustc), "bootstrap", None), + (Some(Mode::ToolRustcPrivate), "bootstrap", None), (Some(Mode::ToolStd), "bootstrap", None), (Some(Mode::Rustc), "llvm_enzyme", None), (Some(Mode::Codegen), "llvm_enzyme", None), - (Some(Mode::ToolRustc), "llvm_enzyme", None), - (Some(Mode::ToolRustc), "rust_analyzer", None), + (Some(Mode::ToolRustcPrivate), "llvm_enzyme", None), + (Some(Mode::ToolRustcPrivate), "rust_analyzer", None), (Some(Mode::ToolStd), "rust_analyzer", None), // Any library specific cfgs like `target_os`, `target_arch` should be put in // priority the `[lints.rust.unexpected_cfgs.check-cfg]` table @@ -334,17 +334,18 @@ pub enum Mode { /// compiletest which needs libtest. ToolStd, - /// Build a tool which uses the locally built rustc and the target std, + /// Build a tool which uses the `rustc_private` mechanism, and thus + /// the locally built rustc rlib artifacts, /// placing the output in the "stageN-tools" directory. This is used for - /// anything that needs a fully functional rustc, such as rustdoc, clippy, - /// cargo, rustfmt, miri, etc. - ToolRustc, + /// everything that links to rustc as a library, such as rustdoc, clippy, + /// rustfmt, miri, etc. + ToolRustcPrivate, } impl Mode { pub fn is_tool(&self) -> bool { match self { - Mode::ToolBootstrap | Mode::ToolRustc | Mode::ToolStd | Mode::ToolTarget => true, + Mode::ToolBootstrap | Mode::ToolRustcPrivate | Mode::ToolStd | Mode::ToolTarget => true, Mode::Std | Mode::Codegen | Mode::Rustc => false, } } @@ -353,7 +354,7 @@ impl Mode { match self { Mode::Std | Mode::Codegen => true, Mode::ToolBootstrap - | Mode::ToolRustc + | Mode::ToolRustcPrivate | Mode::ToolStd | Mode::ToolTarget | Mode::Rustc => false, @@ -924,7 +925,7 @@ impl Build { Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"), Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"), Mode::ToolBootstrap => bootstrap_tool(), - Mode::ToolStd | Mode::ToolRustc => (Some(build_compiler.stage + 1), "tools"), + Mode::ToolStd | Mode::ToolRustcPrivate => (Some(build_compiler.stage + 1), "tools"), Mode::ToolTarget => { // If we're not cross-compiling (the common case), share the target directory with // bootstrap tools to reuse the build cache. @@ -1145,7 +1146,7 @@ impl Build { | Mode::ToolBootstrap | Mode::ToolTarget | Mode::ToolStd - | Mode::ToolRustc, + | Mode::ToolRustcPrivate, ) | None => target_and_stage.stage + 1, }; diff --git a/src/bootstrap/src/utils/cc_detect/tests.rs b/src/bootstrap/src/utils/cc_detect/tests.rs index bed03c18aaa..a6233e6b61c 100644 --- a/src/bootstrap/src/utils/cc_detect/tests.rs +++ b/src/bootstrap/src/utils/cc_detect/tests.rs @@ -3,7 +3,8 @@ use std::{env, iter}; use super::*; use crate::core::config::{Target, TargetSelection}; -use crate::{Build, Config, Flags}; +use crate::utils::tests::TestCtx; +use crate::{Build, Config, Flags, t}; #[test] fn test_ndk_compiler_c() { @@ -68,7 +69,8 @@ fn test_language_clang() { #[test] fn test_new_cc_build() { - let build = Build::new(Config { ..Config::parse(Flags::parse(&["build".to_owned()])) }); + let config = TestCtx::new().config("build").create_config(); + let build = Build::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let cfg = new_cc_build(&build, target.clone()); let compiler = cfg.get_compiler(); @@ -77,7 +79,8 @@ fn test_new_cc_build() { #[test] fn test_default_compiler_wasi() { - let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["build".to_owned()])) }); + let config = TestCtx::new().config("build").create_config(); + let mut build = Build::new(config); let target = TargetSelection::from_user("wasm32-wasi"); let wasi_sdk = PathBuf::from("/wasi-sdk"); build.wasi_sdk_path = Some(wasi_sdk.clone()); @@ -98,7 +101,8 @@ fn test_default_compiler_wasi() { #[test] fn test_default_compiler_fallback() { - let build = Build::new(Config { ..Config::parse(Flags::parse(&["build".to_owned()])) }); + let config = TestCtx::new().config("build").create_config(); + let build = Build::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let mut cfg = cc::Build::new(); let result = default_compiler(&mut cfg, Language::C, target, &build); @@ -107,7 +111,8 @@ fn test_default_compiler_fallback() { #[test] fn test_find_target_with_config() { - let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["build".to_owned()])) }); + let config = TestCtx::new().config("build").create_config(); + let mut build = Build::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let mut target_config = Target::default(); target_config.cc = Some(PathBuf::from("dummy-cc")); @@ -128,7 +133,8 @@ fn test_find_target_with_config() { #[test] fn test_find_target_without_config() { - let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["build".to_owned()])) }); + let config = TestCtx::new().config("build").create_config(); + let mut build = Build::new(config); let target = TargetSelection::from_user("x86_64-unknown-linux-gnu"); build.config.target_config.clear(); fill_target_compiler(&mut build, target.clone()); @@ -141,7 +147,8 @@ fn test_find_target_without_config() { #[test] fn test_find() { - let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["build".to_owned()])) }); + let config = TestCtx::new().config("build").create_config(); + let mut build = Build::new(config); let target1 = TargetSelection::from_user("x86_64-unknown-linux-gnu"); let target2 = TargetSelection::from_user("x86_64-unknown-openbsd"); build.targets.push(target1.clone()); diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 2cc2fb486fa..01309072927 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -536,4 +536,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Warning, summary: "It is no longer possible to `x test` with stage 0, except for running compiletest and opting into `build.compiletest-allow-stage0`.", }, + ChangeInfo { + change_id: 145976, + severity: ChangeSeverity::Info, + summary: "Added a new option `rust.break-on-ice` to control if internal compiler errors cause a debug break on Windows.", + }, ]; diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/writing-tools-in-bootstrap.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/writing-tools-in-bootstrap.md index 41d0cf8d9fb..c3660e24b15 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/writing-tools-in-bootstrap.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/writing-tools-in-bootstrap.md @@ -11,11 +11,8 @@ There are three types of tools you can write in bootstrap: Use this for tools that rely on the locally built std. The output goes into the "stageN-tools" directory. This mode is rarely used, mainly for `compiletest` which requires `libtest`. -- **`Mode::ToolRustc`** - Use this for tools that depend on both the locally built `rustc` and the target `std`. This is more complex than - the other modes because the tool must be built with the same compiler used for `rustc` and placed in the "stageN-tools" - directory. When you choose `Mode::ToolRustc`, `ToolBuild` implementation takes care of this automatically. - If you need to use the builder’s compiler for something specific, you can get it from `ToolBuildResult`, which is +- **`Mode::ToolRustcPrivate`** + Use this for tools that use the `rustc_private` mechanism, and thus depend on the locally built `rustc` and its rlib artifacts. This is more complex than the other modes because the tool must be built with the same compiler used for `rustc` and placed in the "stageN-tools" directory. When you choose `Mode::ToolRustcPrivate`, `ToolBuild` implementation takes care of this automatically. If you need to use the builder’s compiler for something specific, you can get it from `ToolBuildResult`, which is returned by the tool's [`Step`]. Regardless of the tool type you must return `ToolBuildResult` from the tool’s [`Step`] implementation and use `ToolBuild` inside it. diff --git a/src/doc/rustc/src/command-line-arguments/print-options.md b/src/doc/rustc/src/command-line-arguments/print-options.md index fed19d6b667..f37b27d88c3 100644 --- a/src/doc/rustc/src/command-line-arguments/print-options.md +++ b/src/doc/rustc/src/command-line-arguments/print-options.md @@ -184,7 +184,7 @@ Example: ```bash $ rustc --print native-static-libs --crate-type staticlib a.rs -note: Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms. +note: link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms. note: native-static-libs: -lgcc_s -lutil [REDACTED] -lpthread -lm -ldl -lc ``` diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index dfe193e6e8a..6c5b48d8c8f 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -33,6 +33,7 @@ All tier 1 targets with host tools support the full standard library. target | notes -------|------- [`aarch64-apple-darwin`](platform-support/apple-darwin.md) | ARM64 macOS (11.0+, Big Sur+) +[`aarch64-pc-windows-msvc`](platform-support/windows-msvc.md) | ARM64 Windows MSVC `aarch64-unknown-linux-gnu` | ARM64 Linux (kernel 4.1+, glibc 2.17+) [`i686-pc-windows-msvc`](platform-support/windows-msvc.md) | 32-bit MSVC (Windows 10+, Windows Server 2016+, Pentium 4) [^x86_32-floats-return-ABI] [^win32-msvc-alignment] `i686-unknown-linux-gnu` | 32-bit Linux (kernel 3.2+, glibc 2.17+, Pentium 4) [^x86_32-floats-return-ABI] @@ -88,7 +89,6 @@ so Rustup may install the documentation for a similar tier 1 target instead. target | notes -------|------- [`aarch64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | ARM64 MinGW (Windows 10+), LLVM ABI -[`aarch64-pc-windows-msvc`](platform-support/windows-msvc.md) | ARM64 Windows MSVC [`aarch64-unknown-linux-musl`](platform-support/aarch64-unknown-linux-musl.md) | ARM64 Linux with musl 1.2.3 [`aarch64-unknown-linux-ohos`](platform-support/openharmony.md) | ARM64 OpenHarmony `arm-unknown-linux-gnueabi` | Armv6 Linux (kernel 3.2+, glibc 2.17) diff --git a/src/doc/rustc/src/platform-support/windows-msvc.md b/src/doc/rustc/src/platform-support/windows-msvc.md index 71dc4ddc2e6..826c75b79c5 100644 --- a/src/doc/rustc/src/platform-support/windows-msvc.md +++ b/src/doc/rustc/src/platform-support/windows-msvc.md @@ -4,13 +4,10 @@ Windows MSVC targets. **Tier 1 with host tools:** +- `aarch64-pc-windows-msvc`: Windows on ARM64. - `i686-pc-windows-msvc`: Windows on 32-bit x86. - `x86_64-pc-windows-msvc`: Windows on 64-bit x86. -**Tier 2 with host tools:** - -- `aarch64-pc-windows-msvc`: Windows on ARM64. - ## Target maintainers [@ChrisDenton](https://github.com/ChrisDenton) diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py index 8d7f7341c2e..46a3a1602ac 100755 --- a/src/etc/htmldocck.py +++ b/src/etc/htmldocck.py @@ -247,7 +247,7 @@ class CachedFiles(object): paths = list(Path(self.root).glob(path)) if len(paths) != 1: raise FailedCheck("glob path does not resolve to one file") - path = str(paths[0]) + return str(paths[0]) return os.path.join(self.root, path) def get_file(self, path): diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 5d36ffc2d3a..f37a8d85361 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -21,7 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = "1.8.1" -stringdex = { version = "0.0.1-alpha4" } +stringdex = { version = "0.0.1-alpha9" } tempfile = "3" threadpool = "1.8.1" tracing = "0.1" diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 450ac04b40d..d7f6fa347be 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -454,15 +454,22 @@ impl Options { return None; } - let mut emit = Vec::new(); + let mut emit = FxIndexMap::<_, EmitType>::default(); for list in matches.opt_strs("emit") { for kind in list.split(',') { match kind.parse() { - Ok(kind) => emit.push(kind), + Ok(kind) => { + // De-duplicate emit types and the last wins. + // Only one instance for each type is allowed + // regardless the actual data it carries. + // This matches rustc's `--emit` behavior. + emit.insert(std::mem::discriminant(&kind), kind); + } Err(()) => dcx.fatal(format!("unrecognized emission type: {kind}")), } } } + let emit = emit.into_values().collect::<Vec<_>>(); let show_coverage = matches.opt_present("show-coverage"); let output_format_s = matches.opt_str("output-format"); @@ -814,7 +821,8 @@ impl Options { let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx); let with_examples = matches.opt_strs("with-examples"); - let call_locations = crate::scrape_examples::load_call_locations(with_examples, dcx); + let call_locations = + crate::scrape_examples::load_call_locations(with_examples, dcx, &mut loaded_paths); let doctest_build_args = matches.opt_strs("doctest-build-arg"); let unstable_features = diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 5da37c97c6a..b01b596da68 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1212,7 +1212,7 @@ class DocSearch { * will never fulfill. */ async buildIndex() { - const nn = this.database.getIndex("normalizedName"); + const nn = this.database.getData("normalizedName"); if (!nn) { return; } @@ -3722,7 +3722,7 @@ class DocSearch { * @returns {AsyncGenerator<rustdoc.ResultObject>} */ async function*(currentCrate) { - const index = this.database.getIndex("normalizedName"); + const index = this.database.getData("normalizedName"); if (!index) { return; } @@ -3851,8 +3851,7 @@ class DocSearch { }; if (elem.normalizedPathLast === "") { // faster full-table scan for this specific case. - const nameData = this.database.getData("name"); - const l = nameData ? nameData.length : 0; + const l = index.length; for (let id = 0; id < l; ++id) { if (!idDuplicates.has(id)) { idDuplicates.add(id); @@ -3954,7 +3953,7 @@ class DocSearch { * @returns {AsyncGenerator<rustdoc.ResultObject>} */ async function*(inputs, output, typeInfo, currentCrate) { - const index = this.database.getIndex("normalizedName"); + const index = this.database.getData("normalizedName"); if (!index) { return; } diff --git a/src/librustdoc/html/static/js/stringdex.d.ts b/src/librustdoc/html/static/js/stringdex.d.ts index cf9a8b6b564..2eb1fdf95d8 100644 --- a/src/librustdoc/html/static/js/stringdex.d.ts +++ b/src/librustdoc/html/static/js/stringdex.d.ts @@ -5,18 +5,9 @@ declare namespace stringdex { * The client interface to Stringdex. */ interface Database { - getIndex(colname: string): SearchTree|undefined; getData(colname: string): DataColumn|undefined; } /** - * A search index file. - */ - interface SearchTree { - trie(): Trie; - search(name: Uint8Array|string): Promise<Trie?>; - searchLev(name: Uint8Array|string): AsyncGenerator<Trie>; - } - /** * A compressed node in the search tree. * * This object logically addresses two interleaved trees: @@ -29,9 +20,7 @@ declare namespace stringdex { matches(): RoaringBitmap; substringMatches(): AsyncGenerator<RoaringBitmap>; prefixMatches(): AsyncGenerator<RoaringBitmap>; - keys(): Uint8Array; keysExcludeSuffixOnly(): Uint8Array; - children(): [number, Promise<Trie>][]; childrenExcludeSuffixOnly(): [number, Promise<Trie>][]; child(id: number): Promise<Trie>?; } @@ -41,6 +30,8 @@ declare namespace stringdex { interface DataColumn { isEmpty(id: number): boolean; at(id: number): Promise<Uint8Array|undefined>; + search(name: Uint8Array|string): Promise<Trie?>; + searchLev(name: Uint8Array|string): AsyncGenerator<Trie>; length: number, } /** diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js index cb956d926db..b7f605a1035 100644 --- a/src/librustdoc/html/static/js/stringdex.js +++ b/src/librustdoc/html/static/js/stringdex.js @@ -1,3 +1,4 @@ +// ignore-tidy-filelength /** * @import * as stringdex from "./stringdex.d.ts" */ @@ -486,6 +487,63 @@ class RoaringBitmap { return mid === -1 ? false : this.containers[mid].contains(value); } /** + * @param {number} keyvalue + * @returns {RoaringBitmap} + */ + remove(keyvalue) { + const key = keyvalue >> 16; + const value = keyvalue & 0xFFFF; + const mid = this.getContainerId(key); + if (mid === -1) { + return this; + } + const container = this.containers[mid]; + if (!container.contains(value)) { + return this; + } + const newCardinality = (this.keysAndCardinalities[(mid * 4) + 2] | + (this.keysAndCardinalities[(mid * 4) + 3] << 8)); + const l = this.containers.length; + const m = l - (newCardinality === 0 ? 1 : 0); + const result = new RoaringBitmap(null, 0); + result.keysAndCardinalities = new Uint8Array(m * 4); + let j = 0; + for (let i = 0; i < l; i += 1) { + if (i === mid) { + if (newCardinality !== 0) { + result.keysAndCardinalities[(j * 4) + 0] = key; + result.keysAndCardinalities[(j * 4) + 1] = key >> 8; + const card = newCardinality - 1; + result.keysAndCardinalities[(j * 4) + 2] = card; + result.keysAndCardinalities[(j * 4) + 3] = card >> 8; + const newContainer = new RoaringBitmapArray( + newCardinality, + new Uint8Array(newCardinality * 2), + ); + let newContainerSlot = 0; + for (const containerValue of container.values()) { + if (containerValue !== value) { + newContainer.array[newContainerSlot] = value & 0xFF; + newContainerSlot += 1; + newContainer.array[newContainerSlot] = value >> 8; + newContainerSlot += 1; + } + } + result.containers.push(newContainer); + j += 1; + } + } else { + result.keysAndCardinalities[(j * 4) + 0] = this.keysAndCardinalities[(i * 4) + 0]; + result.keysAndCardinalities[(j * 4) + 1] = this.keysAndCardinalities[(i * 4) + 1]; + result.keysAndCardinalities[(j * 4) + 2] = this.keysAndCardinalities[(i * 4) + 2]; + result.keysAndCardinalities[(j * 4) + 3] = this.keysAndCardinalities[(i * 4) + 3]; + result.containers.push(this.containers[i]); + j += 1; + } + } + return result; + } + /** * @param {number} key * @returns {number} */ @@ -878,6 +936,46 @@ function bitCount(n) { /*eslint-enable */ /** + * https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm + */ +class Uint8ArraySearchPattern { + /** @param {Uint8Array} needle */ + constructor(needle) { + this.needle = needle; + this.skipTable = []; + const m = needle.length; + for (let i = 0; i < 256; i += 1) { + this.skipTable.push(m); + } + for (let i = 0; i < m - 1; i += 1) { + this.skipTable[needle[i]] = m - 1 - i; + } + } + /** + * @param {Uint8Array} haystack + * @returns {boolean} + */ + matches(haystack) { + const needle = this.needle; + const skipTable = this.skipTable; + const m = needle.length; + const n = haystack.length; + + let skip = 0; + search: while (n - skip >= m) { + for (let i = m - 1; i >= 0; i -= 1) { + if (haystack[skip + i] !== needle[i]) { + skip += skipTable[haystack[skip + m - 1]]; + continue search; + } + } + return true; + } + return false; + } +} + +/** * @param {stringdex.Hooks} hooks * @returns {Promise<stringdex.Database>} */ @@ -887,12 +985,6 @@ function loadDatabase(hooks) { rr_: function(data) { const dataObj = JSON.parse(data); for (const colName of Object.keys(dataObj)) { - if (Object.hasOwn(dataObj[colName], "I")) { - registry.searchTreeRoots.set( - colName, - makeSearchTreeFromBase64(dataObj[colName].I)[1], - ); - } if (Object.hasOwn(dataObj[colName], "N")) { const counts = []; const countsstring = dataObj[colName]["N"]; @@ -915,6 +1007,9 @@ function loadDatabase(hooks) { makeUint8ArrayFromBase64(dataObj[colName]["H"]), new RoaringBitmap(makeUint8ArrayFromBase64(dataObj[colName]["E"]), 0), colName, + Object.hasOwn(dataObj[colName], "I") ? + makeSearchTreeFromBase64(dataObj[colName].I)[1] : + null, )); } } @@ -978,7 +1073,7 @@ function loadDatabase(hooks) { * searchTreePromises: HashTable<Promise<SearchTree>>; * dataColumnLoadPromiseCallbacks: HashTable<function(any, Uint8Array[]?): any>; * dataColumns: Map<string, DataColumn>; - * dataColumnsBuckets: Map<string, HashTable<Promise<Uint8Array[]>>>; + * dataColumnsBuckets: HashTable<Promise<Uint8Array[]>>; * searchTreeLoadByNodeID: function(Uint8Array): Promise<SearchTree>; * searchTreeRootCallback?: function(any, Database?): any; * dataLoadByNameAndHash: function(string, Uint8Array): Promise<Uint8Array[]>; @@ -990,7 +1085,7 @@ function loadDatabase(hooks) { searchTreePromises: new HashTable(), dataColumnLoadPromiseCallbacks: new HashTable(), dataColumns: new Map(), - dataColumnsBuckets: new Map(), + dataColumnsBuckets: new HashTable(), searchTreeLoadByNodeID: function(nodeid) { const existingPromise = registry.searchTreePromises.get(nodeid); if (existingPromise) { @@ -1022,7 +1117,7 @@ function loadDatabase(hooks) { const data = (nodeid[0] & 0x20) !== 0 ? Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : EMPTY_UINT8; - newPromise = Promise.resolve(new SearchTree( + newPromise = Promise.resolve(new PrefixSearchTree( EMPTY_SEARCH_TREE_BRANCHES, EMPTY_SEARCH_TREE_BRANCHES, data, @@ -1058,12 +1153,7 @@ function loadDatabase(hooks) { return newPromise; }, dataLoadByNameAndHash: function(name, hash) { - let dataColumnBuckets = registry.dataColumnsBuckets.get(name); - if (dataColumnBuckets === undefined) { - dataColumnBuckets = new HashTable(); - registry.dataColumnsBuckets.set(name, dataColumnBuckets); - } - const existingBucket = dataColumnBuckets.get(hash); + const existingBucket = registry.dataColumnsBuckets.get(hash); if (existingBucket) { return existingBucket; } @@ -1091,16 +1181,17 @@ function loadDatabase(hooks) { hooks.loadDataByNameAndHash(name, hashHex); } }); - dataColumnBuckets.set(hash, newBucket); + registry.dataColumnsBuckets.set(hash, newBucket); return newBucket; }, }; /** * The set of child subtrees. + * @template ST * @type {{ * nodeids: Uint8Array, - * subtrees: Array<Promise<SearchTree>|null>, + * subtrees: Array<Promise<ST>|null>, * }} */ class SearchTreeBranches { @@ -1128,7 +1219,7 @@ function loadDatabase(hooks) { ); } // https://github.com/microsoft/TypeScript/issues/17227 - /** @returns {Generator<[number, Promise<SearchTree>|null]>} */ + /** @returns {Generator<[number, Promise<ST>|null]>} */ entries() { throw new Error(); } @@ -1157,10 +1248,12 @@ function loadDatabase(hooks) { /** * A sorted array of search tree branches. * + * @template ST + * @extends SearchTreeBranches<ST> * @type {{ * keys: Uint8Array, * nodeids: Uint8Array, - * subtrees: Array<Promise<SearchTree>|null>, + * subtrees: Array<Promise<ST>|null>, * }} */ class SearchTreeBranchesArray extends SearchTreeBranches { @@ -1179,7 +1272,7 @@ function loadDatabase(hooks) { i += 1; } } - /** @returns {Generator<[number, Promise<SearchTree>|null]>} */ + /** @returns {Generator<[number, Promise<ST>|null]>} */ * entries() { let i = 0; const l = this.keys.length; @@ -1246,12 +1339,16 @@ function loadDatabase(hooks) { } /** + * @template ST * @param {number[]} alphabitmap_chars * @param {number} width - * @return {(typeof SearchTreeBranches)&{"ALPHABITMAP_CHARS": number[], "width": number}} + * @return {(typeof SearchTreeBranches<ST>)&{"ALPHABITMAP_CHARS": number[], "width": number}} */ function makeSearchTreeBranchesAlphaBitmapClass(alphabitmap_chars, width) { const bitwidth = width * 8; + /** + * @extends SearchTreeBranches<ST> + */ const cls = class SearchTreeBranchesAlphaBitmap extends SearchTreeBranches { /** * @param {number} bitmap @@ -1265,7 +1362,7 @@ function loadDatabase(hooks) { this.bitmap = bitmap; this.nodeids = nodeids; } - /** @returns {Generator<[number, Promise<SearchTree>|null]>} */ + /** @returns {Generator<[number, Promise<ST>|null]>} */ * entries() { let i = 0; let j = 0; @@ -1295,15 +1392,7 @@ function loadDatabase(hooks) { * @returns {number} */ getKey(branch_index) { - //return this.getKeys()[branch_index]; - let alpha_index = 0; - while (branch_index >= 0) { - if (this.bitmap & (1 << alpha_index)) { - branch_index -= 1; - } - alpha_index += 1; - } - return alphabitmap_chars[alpha_index]; + return this.getKeys()[branch_index]; } /** * @returns {Uint8Array} @@ -1326,20 +1415,35 @@ function loadDatabase(hooks) { return cls; } + /** + * @template ST + * @type {(typeof SearchTreeBranches<any>)&{"ALPHABITMAP_CHARS": number[], "width": number}} + */ const SearchTreeBranchesShortAlphaBitmap = makeSearchTreeBranchesAlphaBitmapClass(SHORT_ALPHABITMAP_CHARS, 3); + /** + * @template ST + * @type {(typeof SearchTreeBranches<any>)&{"ALPHABITMAP_CHARS": number[], "width": number}} + */ const SearchTreeBranchesLongAlphaBitmap = makeSearchTreeBranchesAlphaBitmapClass(LONG_ALPHABITMAP_CHARS, 4); /** - * A [suffix tree], used for name-based search. + * @typedef {PrefixSearchTree|SuffixSearchTree} SearchTree + * @typedef {PrefixTrie|SuffixTrie} Trie + */ + + /** + * An interleaved [prefix] and [suffix tree], + * used for name-based search. * - * This data structure is used to drive substring matches, + * This data structure is used to drive prefix matches, * such as matching the query "link" to `LinkedList`, * and Lev-distance matches, such as matching the * query "hahsmap" to `HashMap`. * + * [prefix tree]: https://en.wikipedia.org/wiki/Prefix_tree * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree * * branches @@ -1358,17 +1462,17 @@ function loadDatabase(hooks) { * will include these. * * @type {{ - * might_have_prefix_branches: SearchTreeBranches, - * branches: SearchTreeBranches, + * might_have_prefix_branches: SearchTreeBranches<SearchTree>, + * branches: SearchTreeBranches<SearchTree>, * data: Uint8Array, * leaves_suffix: RoaringBitmap, * leaves_whole: RoaringBitmap, * }} */ - class SearchTree { + class PrefixSearchTree { /** - * @param {SearchTreeBranches} branches - * @param {SearchTreeBranches} might_have_prefix_branches + * @param {SearchTreeBranches<SearchTree>} branches + * @param {SearchTreeBranches<SearchTree>} might_have_prefix_branches * @param {Uint8Array} data * @param {RoaringBitmap} leaves_whole * @param {RoaringBitmap} leaves_suffix @@ -1392,25 +1496,31 @@ function loadDatabase(hooks) { * A Trie pointer refers to a single node in a logical decompressed search tree * (the real search tree is compressed). * - * @return {Trie} + * @param {DataColumn} dataColumn + * @param {Uint8ArraySearchPattern} searchPattern + * @return {PrefixTrie} */ - trie() { - return new Trie(this, 0); + trie(dataColumn, searchPattern) { + return new PrefixTrie(this, 0, dataColumn, searchPattern); } /** * Return the trie representing `name` * @param {Uint8Array|string} name + * @param {DataColumn} dataColumn * @returns {Promise<Trie?>} */ - async search(name) { + async search(name, dataColumn) { if (typeof name === "string") { const utf8encoder = new TextEncoder(); name = utf8encoder.encode(name); } - let trie = this.trie(); + const searchPattern = new Uint8ArraySearchPattern(name); + /** @type {Trie} */ + let trie = this.trie(dataColumn, searchPattern); for (const datum of name) { // code point definitely exists + /** @type {Promise<Trie>?} */ const newTrie = trie.child(datum); if (newTrie) { trie = await newTrie; @@ -1423,26 +1533,28 @@ function loadDatabase(hooks) { /** * @param {Uint8Array|string} name + * @param {DataColumn} dataColumn * @returns {AsyncGenerator<Trie>} */ - async* searchLev(name) { + async* searchLev(name, dataColumn) { if (typeof name === "string") { const utf8encoder = new TextEncoder(); name = utf8encoder.encode(name); } const w = name.length; if (w < 3) { - const trie = await this.search(name); + const trie = await this.search(name, dataColumn); if (trie !== null) { yield trie; } return; } + const searchPattern = new Uint8ArraySearchPattern(name); const levParams = w >= 6 ? new Lev2TParametricDescription(w) : new Lev1TParametricDescription(w); /** @type {Array<[Promise<Trie>, number]>} */ - const stack = [[Promise.resolve(this.trie()), 0]]; + const stack = [[Promise.resolve(this.trie(dataColumn, searchPattern)), 0]]; const n = levParams.n; while (stack.length !== 0) { // It's not empty @@ -1475,20 +1587,29 @@ function loadDatabase(hooks) { } } } + + /** @returns {RoaringBitmap} */ + getCurrentLeaves() { + return this.leaves_whole.union(this.leaves_suffix); + } } /** * A representation of a set of strings in the search index, * as a subset of the entire tree. */ - class Trie { + class PrefixTrie { /** - * @param {SearchTree} tree + * @param {PrefixSearchTree} tree * @param {number} offset + * @param {DataColumn} dataColumn + * @param {Uint8ArraySearchPattern} searchPattern */ - constructor(tree, offset) { + constructor(tree, offset, dataColumn, searchPattern) { this.tree = tree; this.offset = offset; + this.dataColumn = dataColumn; + this.searchPattern = searchPattern; } /** @@ -1514,7 +1635,28 @@ function loadDatabase(hooks) { const current_layer = layer; layer = []; for await (const tree of current_layer) { - yield tree.leaves_whole.union(tree.leaves_suffix); + /** @type {number[]?} */ + let rejected = null; + let leaves = tree.getCurrentLeaves(); + for (const leaf of leaves.entries()) { + const haystack = await this.dataColumn.at(leaf); + if (haystack === undefined || !this.searchPattern.matches(haystack)) { + if (!rejected) { + rejected = []; + } + rejected.push(leaf); + } + } + if (rejected) { + if (leaves.cardinality() !== rejected.length) { + for (const rej of rejected) { + leaves = leaves.remove(rej); + } + yield leaves; + } + } else { + yield leaves; + } } /** @type {HashTable<[number, SearchTree][]>} */ const subnodes = new HashTable(); @@ -1548,12 +1690,14 @@ function loadDatabase(hooks) { const res = registry.searchTreeLoadByNodeID(newnode); for (const [byte, node] of subnode_list) { const branches = node.branches; - const might_have_prefix_branches = node.might_have_prefix_branches; const i = branches.getIndex(byte); branches.subtrees[i] = res; - const mhpI = might_have_prefix_branches.getIndex(byte); - if (mhpI !== -1) { - might_have_prefix_branches.subtrees[mhpI] = res; + if (node instanceof PrefixSearchTree) { + const might_have_prefix_branches = node.might_have_prefix_branches; + const mhpI = might_have_prefix_branches.getIndex(byte); + if (mhpI !== -1) { + might_have_prefix_branches.subtrees[mhpI] = res; + } } } layer.push(res); @@ -1581,6 +1725,9 @@ function loadDatabase(hooks) { // if we want to do them in order) for (const {node, len} of current_layer) { const tree = await node; + if (!(tree instanceof PrefixSearchTree)) { + continue; + } const length = len + tree.data.length; if (minLength === null || length < minLength) { minLength = length; @@ -1637,10 +1784,13 @@ function loadDatabase(hooks) { } } // if we still have more subtrees to walk, then keep going - /** @type {HashTable<{byte: number, tree: SearchTree, len: number}[]>} */ + /** @type {HashTable<{byte: number, tree: PrefixSearchTree, len: number}[]>} */ const subnodes = new HashTable(); for await (const {node, len} of current_layer) { const tree = await node; + if (!(tree instanceof PrefixSearchTree)) { + continue; + } const length = len + tree.data.length; const mhp_branches = tree.might_have_prefix_branches; const l = mhp_branches.subtrees.length; @@ -1663,8 +1813,6 @@ function loadDatabase(hooks) { subnode_list.push({byte, tree, len}); } } - } else { - throw new Error(`malformed tree; index ${i} does not exist`); } } } @@ -1728,14 +1876,21 @@ function loadDatabase(hooks) { this.tree.might_have_prefix_branches.subtrees[mhpI] = node; } } - nodes.push([k, node.then(node => node.trie())]); + nodes.push([k, node.then(node => { + return node.trie(this.dataColumn, this.searchPattern); + })]); i += 1; } return nodes; } else { /** @type {number} */ const codePoint = data[this.offset]; - const trie = new Trie(this.tree, this.offset + 1); + const trie = new PrefixTrie( + this.tree, + this.offset + 1, + this.dataColumn, + this.searchPattern, + ); return [[codePoint, Promise.resolve(trie)]]; } } @@ -1777,14 +1932,21 @@ function loadDatabase(hooks) { this.tree.might_have_prefix_branches.subtrees[i] = node; this.tree.branches.subtrees[this.tree.branches.getIndex(k)] = node; } - nodes.push([k, node.then(node => node.trie())]); + nodes.push([k, node.then(node => { + return node.trie(this.dataColumn, this.searchPattern); + })]); i += 1; } return nodes; } else { /** @type {number} */ const codePoint = data[this.offset]; - const trie = new Trie(this.tree, this.offset + 1); + const trie = new PrefixTrie( + this.tree, + this.offset + 1, + this.dataColumn, + this.searchPattern, + ); return [[codePoint, Promise.resolve(trie)]]; } } @@ -1811,10 +1973,275 @@ function loadDatabase(hooks) { this.tree.might_have_prefix_branches.subtrees[mhpI] = branch; } } - return branch.then(branch => branch.trie()); + return branch.then(branch => branch.trie(this.dataColumn, this.searchPattern)); } } else if (this.tree.data[this.offset] === byte) { - return Promise.resolve(new Trie(this.tree, this.offset + 1)); + return Promise.resolve(new PrefixTrie( + this.tree, + this.offset + 1, + this.dataColumn, + this.searchPattern, + )); + } + return null; + } + } + /** + * A [suffix tree], used for name-based search. + * + * This data structure is used to drive substring matches, + * such as matching the query "inked" to `LinkedList`. + * + * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree + * + * Suffix trees do not actually carry the intermediate data + * between branches, so, in order to validate the results, + * they must go through a filtering step at the end. + * Suffix trees also cannot match lev and exact matches, + * so those just return empty sets. + * + * branches + * : A sorted-array map of subtrees. + * + * dataLen + * : The length of the substring used by this node. + * + * leaves_suffix + * : The IDs of every entry that matches. Levenshtein matches + * won't include these. + * + * @type {{ + * branches: SearchTreeBranches<SearchTree>, + * dataLen: number, + * leaves_suffix: RoaringBitmap, + * }} + */ + class SuffixSearchTree { + /** + * @param {SearchTreeBranches<SearchTree>} branches + * @param {number} dataLen + * @param {RoaringBitmap} leaves_suffix + */ + constructor( + branches, + dataLen, + leaves_suffix, + ) { + this.branches = branches; + this.dataLen = dataLen; + this.leaves_suffix = leaves_suffix; + } + /** + * Returns the Trie for the root node. + * + * A Trie pointer refers to a single node in a logical decompressed search tree + * (the real search tree is compressed). + * + * @param {DataColumn} dataColumn + * @param {Uint8ArraySearchPattern} searchPattern + * @return {Trie} + */ + trie(dataColumn, searchPattern) { + return new SuffixTrie(this, 0, dataColumn, searchPattern); + } + + /** + * Return the trie representing `name` + * @param {Uint8Array|string} name + * @param {DataColumn} dataColumn + * @returns {Promise<Trie?>} + */ + async search(name, dataColumn) { + if (typeof name === "string") { + const utf8encoder = new TextEncoder(); + name = utf8encoder.encode(name); + } + const searchPattern = new Uint8ArraySearchPattern(name); + let trie = this.trie(dataColumn, searchPattern); + for (const datum of name) { + // code point definitely exists + const newTrie = trie.child(datum); + if (newTrie) { + trie = await newTrie; + } else { + return null; + } + } + return trie; + } + + /** + * @param {Uint8Array|string} _name + * @param {DataColumn} _dataColumn + * @returns {AsyncGenerator<Trie>} + */ + async* searchLev(_name, _dataColumn) { + // this function only returns whole-string matches, + // which pure-suffix nodes don't have, so is + // intentionally blank + } + + /** @returns {RoaringBitmap} */ + getCurrentLeaves() { + return this.leaves_suffix; + } + } + + /** + * A representation of a set of strings in the search index, + * as a subset of the entire tree (suffix-only). + */ + class SuffixTrie { + /** + * @param {SuffixSearchTree} tree + * @param {number} offset + * @param {DataColumn} dataColumn + * @param {Uint8ArraySearchPattern} searchPattern + */ + constructor(tree, offset, dataColumn, searchPattern) { + this.tree = tree; + this.offset = offset; + this.dataColumn = dataColumn; + this.searchPattern = searchPattern; + } + + /** + * All exact matches for the string represented by this node. + * Since pure-suffix nodes have no exactly-matching children, + * this function returns the empty bitmap. + * @returns {RoaringBitmap} + */ + matches() { + return EMPTY_BITMAP; + } + + /** + * All matches for strings that contain the string represented by this node. + * @returns {AsyncGenerator<RoaringBitmap>} + */ + async* substringMatches() { + /** @type {Promise<SearchTree>[]} */ + let layer = [Promise.resolve(this.tree)]; + while (layer.length) { + const current_layer = layer; + layer = []; + for await (const tree of current_layer) { + /** @type {number[]?} */ + let rejected = null; + let leaves = tree.getCurrentLeaves(); + for (const leaf of leaves.entries()) { + const haystack = await this.dataColumn.at(leaf); + if (haystack === undefined || !this.searchPattern.matches(haystack)) { + if (!rejected) { + rejected = []; + } + rejected.push(leaf); + } + } + if (rejected) { + if (leaves.cardinality() !== rejected.length) { + for (const rej of rejected) { + leaves = leaves.remove(rej); + } + yield leaves; + } + } else { + yield leaves; + } + } + /** @type {HashTable<[number, SearchTree][]>} */ + const subnodes = new HashTable(); + for await (const node of current_layer) { + const branches = node.branches; + const l = branches.subtrees.length; + for (let i = 0; i < l; ++i) { + const subtree = branches.subtrees[i]; + if (subtree) { + layer.push(subtree); + } else if (subtree === null) { + const newnode = branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no node for index ${i}`); + } else { + let subnode_list = subnodes.get(newnode); + if (!subnode_list) { + subnode_list = [[i, node]]; + subnodes.set(newnode, subnode_list); + } else { + subnode_list.push([i, node]); + } + } + } else { + throw new Error(`malformed tree; index ${i} does not exist`); + } + } + } + for (const [newnode, subnode_list] of subnodes.entries()) { + const res = registry.searchTreeLoadByNodeID(newnode); + for (const [i, node] of subnode_list) { + const branches = node.branches; + branches.subtrees[i] = res; + } + layer.push(res); + } + } + } + + /** + * All matches for strings that start with the string represented by this node. + * Since this is a pure-suffix node, there aren't any. + * @returns {AsyncGenerator<RoaringBitmap>} + */ + async* prefixMatches() { + // this function only returns prefix matches, + // which pure-suffix nodes don't have, so is + // intentionally blank + } + + /** + * Returns all keys that are children of this node. + * @returns {Uint8Array} + */ + keysExcludeSuffixOnly() { + return EMPTY_UINT8; + } + + /** + * Returns all nodes that are direct children of this node. + * @returns {[number, Promise<Trie>][]} + */ + childrenExcludeSuffixOnly() { + return []; + } + + /** + * Returns a single node that is a direct child of this node. + * @param {number} byte + * @returns {Promise<Trie>?} + */ + child(byte) { + if (this.offset === this.tree.dataLen) { + const i = this.tree.branches.getIndex(byte); + if (i !== -1) { + /** @type {Promise<SearchTree>?} */ + let branch = this.tree.branches.subtrees[i]; + if (branch === null) { + const newnode = this.tree.branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no node for key ${byte}`); + } + branch = registry.searchTreeLoadByNodeID(newnode); + this.tree.branches.subtrees[i] = branch; + } + return branch.then(branch => branch.trie(this.dataColumn, this.searchPattern)); + } + } else { + return Promise.resolve(new SuffixTrie( + this.tree, + this.offset + 1, + this.dataColumn, + this.searchPattern, + )); } return null; } @@ -1827,8 +2254,10 @@ function loadDatabase(hooks) { * @param {Uint8Array} hashes * @param {RoaringBitmap} emptyset * @param {string} name + * @param {SearchTree?} searchTree */ - constructor(counts, hashes, emptyset, name) { + constructor(counts, hashes, emptyset, name, searchTree) { + this.searchTree = searchTree; this.hashes = hashes; this.emptyset = emptyset; this.name = name; @@ -1883,7 +2312,7 @@ function loadDatabase(hooks) { const {hash, end} = this.buckets[idx]; let data = this.buckets[idx].data; if (data === null) { - const dataSansEmptyset = await registry.dataLoadByNameAndHash( + const dataSansEmptysetOrig = await registry.dataLoadByNameAndHash( this.name, hash, ); @@ -1893,6 +2322,7 @@ function loadDatabase(hooks) { if (data !== null) { return (await data)[id - start]; } + const dataSansEmptyset = [...dataSansEmptysetOrig]; /** @type {(Uint8Array[])|null} */ let dataWithEmptyset = null; let pos = start; @@ -1924,6 +2354,24 @@ function loadDatabase(hooks) { } } } + /** + * Search by exact substring + * @param {Uint8Array|string} name + * @returns {Promise<Trie?>} + */ + async search(name) { + return await (this.searchTree ? this.searchTree.search(name, this) : null); + } + /** + * Search by whole, inexact match + * @param {Uint8Array|string} name + * @returns {AsyncGenerator<Trie>} + */ + async *searchLev(name) { + if (this.searchTree) { + yield* this.searchTree.searchLev(name, this); + } + } } class Database { @@ -2015,7 +2463,7 @@ function loadDatabase(hooks) { const data_history = []; let canonical = EMPTY_UINT8; /** @type {SearchTree} */ - let tree = new SearchTree( + let tree = new PrefixSearchTree( EMPTY_SEARCH_TREE_BRANCHES, EMPTY_SEARCH_TREE_BRANCHES, EMPTY_UINT8, @@ -2029,8 +2477,8 @@ function loadDatabase(hooks) { * @returns {{ * "cpbranches": Uint8Array, * "csbranches": Uint8Array, - * "might_have_prefix_branches": SearchTreeBranches, - * "branches": SearchTreeBranches, + * "might_have_prefix_branches": SearchTreeBranches<SearchTree>, + * "branches": SearchTreeBranches<SearchTree>, * "cpnodes": Uint8Array, * "csnodes": Uint8Array, * "consumed_len_bytes": number, @@ -2051,6 +2499,12 @@ function loadDatabase(hooks) { const start_point = i; let cplen; let cslen; + /** + * @type {( + * typeof SearchTreeBranches<SearchTree> & + * {"ALPHABITMAP_CHARS": number[], "width": number} + * )?} + */ let alphabitmap = null; if (is_pure_suffixes_only_node) { cplen = 0; @@ -2299,6 +2753,8 @@ function loadDatabase(hooks) { if (is_data_compressed) { data = data_history[data_history.length - dlen - 1]; dlen = data.length; + } else if (is_pure_suffixes_only_node) { + data = EMPTY_UINT8; } else { data = dlen === 0 ? EMPTY_UINT8 : @@ -2319,80 +2775,109 @@ function loadDatabase(hooks) { let whole; let suffix; if (is_pure_suffixes_only_node) { - whole = EMPTY_BITMAP; suffix = input[i] === 0 ? EMPTY_BITMAP1 : new RoaringBitmap(input, i); i += suffix.consumed_len_bytes; - } else if (input[i] === 0xff) { - whole = EMPTY_BITMAP; - suffix = EMPTY_BITMAP1; - i += 1; + tree = new SuffixSearchTree( + branches, + dlen, + suffix, + ); + const clen = ( + 3 + // lengths of children and data + csnodes.length + + csbranches.length + + suffix.consumed_len_bytes + ); + if (canonical.length < clen) { + canonical = new Uint8Array(clen); + } + let ci = 0; + canonical[ci] = 1; + ci += 1; + canonical[ci] = dlen; + ci += 1; + canonical[ci] = input[coffset]; // suffix child count + ci += 1; + canonical.set(csnodes, ci); + ci += csnodes.length; + canonical.set(csbranches, ci); + ci += csbranches.length; + const leavesOffset = i - suffix.consumed_len_bytes; + for (let j = leavesOffset; j < i; j += 1) { + canonical[ci + j - leavesOffset] = input[j]; + } + siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); } else { - whole = input[i] === 0 ? - EMPTY_BITMAP1 : - new RoaringBitmap(input, i); - i += whole.consumed_len_bytes; - suffix = input[i] === 0 ? - EMPTY_BITMAP1 : - new RoaringBitmap(input, i); - i += suffix.consumed_len_bytes; - } - tree = new SearchTree( - branches, - might_have_prefix_branches, - data, - whole, - suffix, - ); - const clen = ( - (is_pure_suffixes_only_node ? 3 : 4) + // lengths of children and data - dlen + - cpnodes.length + csnodes.length + - cpbranches.length + csbranches.length + - whole.consumed_len_bytes + - suffix.consumed_len_bytes - ); - if (canonical.length < clen) { - canonical = new Uint8Array(clen); - } - let ci = 0; - canonical[ci] = is_pure_suffixes_only_node ? 1 : 0; - ci += 1; - canonical[ci] = dlen; - ci += 1; - canonical.set(data, ci); - ci += dlen; - canonical[ci] = input[coffset]; - ci += 1; - if (!is_pure_suffixes_only_node) { - canonical[ci] = input[coffset + 1]; + if (input[i] === 0xff) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP1; + i += 1; + } else { + whole = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += whole.consumed_len_bytes; + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } + tree = new PrefixSearchTree( + branches, + might_have_prefix_branches, + data, + whole, + suffix, + ); + const clen = ( + 4 + // lengths of children and data + dlen + + cpnodes.length + csnodes.length + + cpbranches.length + csbranches.length + + whole.consumed_len_bytes + + suffix.consumed_len_bytes + ); + if (canonical.length < clen) { + canonical = new Uint8Array(clen); + } + let ci = 0; + canonical[ci] = 0; ci += 1; + canonical[ci] = dlen; + ci += 1; + canonical.set(data, ci); + ci += data.length; + canonical[ci] = input[coffset]; // prefix child count + ci += 1; + canonical[ci] = input[coffset + 1]; // suffix child count + ci += 1; + canonical.set(cpnodes, ci); + ci += cpnodes.length; + canonical.set(csnodes, ci); + ci += csnodes.length; + canonical.set(cpbranches, ci); + ci += cpbranches.length; + canonical.set(csbranches, ci); + ci += csbranches.length; + const leavesOffset = i - whole.consumed_len_bytes - suffix.consumed_len_bytes; + for (let j = leavesOffset; j < i; j += 1) { + canonical[ci + j - leavesOffset] = input[j]; + } + siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); } - canonical.set(cpnodes, ci); - ci += cpnodes.length; - canonical.set(csnodes, ci); - ci += csnodes.length; - canonical.set(cpbranches, ci); - ci += cpbranches.length; - canonical.set(csbranches, ci); - ci += csbranches.length; - const leavesOffset = i - whole.consumed_len_bytes - suffix.consumed_len_bytes; - for (let j = leavesOffset; j < i; j += 1) { - canonical[ci + j - leavesOffset] = input[j]; - } - siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); hash[2] &= 0x7f; } else { // uncompressed node const dlen = input [i + 1]; i += 2; - if (dlen === 0) { + if (dlen === 0 || is_pure_suffixes_only_node) { data = EMPTY_UINT8; } else { data = new Uint8Array(input.buffer, i + input.byteOffset, dlen); + i += dlen; } - i += dlen; const { consumed_len_bytes: branches_consumed_len_bytes, branches, @@ -2427,13 +2912,19 @@ function loadDatabase(hooks) { i - start, ), 0, 0, 0, 0, hash); hash[2] &= 0x7f; - tree = new SearchTree( - branches, - might_have_prefix_branches, - data, - whole, - suffix, - ); + tree = is_pure_suffixes_only_node ? + new SuffixSearchTree( + branches, + dlen, + suffix, + ) : + new PrefixSearchTree( + branches, + might_have_prefix_branches, + data, + whole, + suffix, + ); } hash_history.push({hash: truncatedHash.slice(), used: false}); if (data.length !== 0) { @@ -2454,20 +2945,22 @@ function loadDatabase(hooks) { } j += 1; } - const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids; - const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees; - j = 0; - lb = tree.might_have_prefix_branches.subtrees.length; - while (j < lb) { - // node id with a 1 in its most significant bit is inlined, and, so - // it won't be in the stash - if ((tree_mhp_branch_nodeids[j * 6] & 0x80) === 0) { - const subtree = stash.getWithOffsetKey(tree_mhp_branch_nodeids, j * 6); - if (subtree !== undefined) { - tree_mhp_branch_subtrees[j] = Promise.resolve(subtree); + if (tree instanceof PrefixSearchTree) { + const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids; + const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees; + j = 0; + lb = tree.might_have_prefix_branches.subtrees.length; + while (j < lb) { + // node id with a 1 in its most significant bit is inlined, and, so + // it won't be in the stash + if ((tree_mhp_branch_nodeids[j * 6] & 0x80) === 0) { + const subtree = stash.getWithOffsetKey(tree_mhp_branch_nodeids, j * 6); + if (subtree !== undefined) { + tree_mhp_branch_subtrees[j] = Promise.resolve(subtree); + } } + j += 1; } - j += 1; } if (i !== l) { stash.set(truncatedHash, tree); diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index 16034c11827..471e966e2c2 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -333,14 +333,11 @@ pub(crate) fn run( pub(crate) fn load_call_locations( with_examples: Vec<String>, dcx: DiagCtxtHandle<'_>, + loaded_paths: &mut Vec<PathBuf>, ) -> AllCallLocations { let mut all_calls: AllCallLocations = FxIndexMap::default(); for path in with_examples { - // FIXME: Figure out why this line is causing this feature to crash in specific contexts. - // Full issue backlog is available here: <https://github.com/rust-lang/rust/pull/144600>. - // - // Can be checked with `tests/run-make/rustdoc-scrape-examples-paths`. - // loaded_paths.push(path.clone().into()); + loaded_paths.push(path.clone().into()); let bytes = match fs::read(&path) { Ok(bytes) => bytes, Err(e) => dcx.fatal(format!("failed to load examples: {e}")), diff --git a/src/tools/cargo b/src/tools/cargo -Subproject a6c58d43051d01d83f55a3e61ef5f5b2b0dd6bd +Subproject 761c4658d0079d607e6d33cf0c060e61a617cad diff --git a/src/tools/clippy/.cargo/config.toml b/src/tools/clippy/.cargo/config.toml index d9c635df5dc..a09bf95e87b 100644 --- a/src/tools/clippy/.cargo/config.toml +++ b/src/tools/clippy/.cargo/config.toml @@ -23,3 +23,10 @@ split-debuginfo = "unpacked" rustflags = ["--remap-path-prefix", "=clippy_dev"] [profile.dev.package.lintcheck] rustflags = ["--remap-path-prefix", "=lintcheck"] + +# quine-mc_cluskey makes up a significant part of the runtime in dogfood +# due to the number of conditions in the clippy_lints crate +# and enabling optimizations for that specific dependency helps a bit +# without increasing total build times. +[profile.dev.package.quine-mc_cluskey] +opt-level = 3 diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index bc60b1c57f7..eb2a76a8183 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -30,6 +30,8 @@ Current stable, released 2025-08-07 * Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`, `transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc) [#14703](https://github.com/rust-lang/rust-clippy/pull/14703) +* Move [`uninlined_format_args`] to `pedantic` (from `style`, now allow-by-default) + [#15287](https://github.com/rust-lang/rust-clippy/pull/15287) ### Enhancements @@ -74,6 +76,9 @@ Current stable, released 2025-08-07 [#14719](https://github.com/rust-lang/rust-clippy/pull/14719) * [`unnecessary_to_owned`] fixed FP when map key is a reference [#14834](https://github.com/rust-lang/rust-clippy/pull/14834) +* [`swap_with_temporary`]: fix false positive leading to different semantics + being suggested, and use the right number of dereferences in suggestion + [#15172](https://github.com/rust-lang/rust-clippy/pull/15172) ### ICE Fixes @@ -6707,6 +6712,7 @@ Released 2018-09-13 [`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers [`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items [`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold +[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold [`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros [`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods [`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names diff --git a/src/tools/clippy/book/src/lint_configuration.md b/src/tools/clippy/book/src/lint_configuration.md index 05590ff7b1c..c2d080cd96a 100644 --- a/src/tools/clippy/book/src/lint_configuration.md +++ b/src/tools/clippy/book/src/lint_configuration.md @@ -485,6 +485,16 @@ The maximum cognitive complexity a function can have * [`cognitive_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity) +## `const-literal-digits-threshold` +The minimum digits a const float literal must have to supress the `excessive_precicion` lint + +**Default Value:** `30` + +--- +**Affected lints:** +* [`excessive_precision`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision) + + ## `disallowed-macros` The list of disallowed macros, written as fully qualified paths. @@ -555,7 +565,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** @@ -873,7 +883,6 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow) * [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics) * [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref) -* [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or) * [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr) * [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark) * [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names) @@ -881,7 +890,6 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`repeat_vec_with_capacity`](https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity) * [`same_item_push`](https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push) * [`seek_from_current`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current) -* [`seek_rewind`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind) * [`to_digit_is_some`](https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some) * [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref) * [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions) diff --git a/src/tools/clippy/clippy.toml b/src/tools/clippy/clippy.toml index 77573105d86..d9bcfd17606 100644 --- a/src/tools/clippy/clippy.toml +++ b/src/tools/clippy/clippy.toml @@ -6,12 +6,12 @@ lint-commented-code = true [[disallowed-methods]] path = "rustc_lint::context::LintContext::lint" -reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" +reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead" [[disallowed-methods]] path = "rustc_lint::context::LintContext::span_lint" -reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" +reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead" [[disallowed-methods]] path = "rustc_middle::ty::context::TyCtxt::node_span_lint" -reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead" +reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead" diff --git a/src/tools/clippy/clippy_config/src/conf.rs b/src/tools/clippy/clippy_config/src/conf.rs index 2ad3f2efcdd..2f28f6175ad 100644 --- a/src/tools/clippy/clippy_config/src/conf.rs +++ b/src/tools/clippy/clippy_config/src/conf.rs @@ -33,6 +33,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", + "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", @@ -569,6 +570,9 @@ define_Conf! { /// The maximum cognitive complexity a function can have #[lints(cognitive_complexity)] cognitive_complexity_threshold: u64 = 25, + /// The minimum digits a const float literal must have to supress the `excessive_precicion` lint + #[lints(excessive_precision)] + const_literal_digits_threshold: usize = 30, /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. /// /// Use the Cognitive Complexity lint instead. @@ -775,7 +779,6 @@ define_Conf! { needless_borrow, non_std_lazy_statics, option_as_ref_deref, - option_map_unwrap_or, ptr_as_ptr, question_mark, redundant_field_names, @@ -783,7 +786,6 @@ define_Conf! { repeat_vec_with_capacity, same_item_push, seek_from_current, - seek_rewind, to_digit_is_some, transmute_ptr_to_ref, tuple_array_conversions, diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml index b9e6de79cc0..70184ee2ca5 100644 --- a/src/tools/clippy/clippy_lints/Cargo.toml +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -27,6 +27,10 @@ url = "2.2" [dev-dependencies] walkdir = "2.3" +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(bootstrap)'] + [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] rustc_private = true diff --git a/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs b/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs index 6f2a6a36a38..08253b0c499 100644 --- a/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs +++ b/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs @@ -3,10 +3,10 @@ use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_no use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; use clippy_utils::usage::local_used_after_expr; -use clippy_utils::{is_expr_final_block_expr, path_res, sym}; +use clippy_utils::{path_res, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; @@ -77,17 +77,20 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { _ => return, }; span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| { - let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; let mut app = Applicability::MachineApplicable; - diag.span_suggestion( - macro_call.span, - "replace with", - format!( - "{}.{replacement}(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + let recv = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; + + // `assert!` doesn't return anything, but `Result::unwrap(_err)` does, so we might need to add a + // semicolon to the suggestion to avoid leaking the type + let sugg = match cx.tcx.parent_hir_node(e.hir_id) { + // trailing expr of a block + Node::Block(..) => format!("{recv}.{replacement}();"), + // already has a trailing semicolon + Node::Stmt(..) => format!("{recv}.{replacement}()"), + // this is the last-resort option, because it's rather verbose + _ => format!("{{ {recv}.{replacement}(); }}"), + }; + diag.span_suggestion(macro_call.span, "replace with", sugg, app); }); } } diff --git a/src/tools/clippy/clippy_lints/src/async_yields_async.rs b/src/tools/clippy/clippy_lints/src/async_yields_async.rs index 013819b0da8..1a10db291cd 100644 --- a/src/tools/clippy/clippy_lints/src/async_yields_async.rs +++ b/src/tools/clippy/clippy_lints/src/async_yields_async.rs @@ -1,8 +1,12 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::snippet; +use clippy_utils::is_expr_async_block; +use clippy_utils::source::walk_span_to_context; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; -use rustc_hir::{Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath}; +use rustc_hir::{ + Block, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -87,31 +91,37 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { let expr_ty = typeck_results.expr_ty(body_expr); if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { - let return_expr_span = match &body_expr.kind { - // XXXkhuey there has to be a better way. - ExprKind::Block(block, _) => block.expr.map(|e| e.span), - ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span), - _ => None, + let (return_expr, return_expr_span) = match &body_expr.kind { + ExprKind::Block(Block { expr: Some(e), .. }, _) => (*e, e.span), + ExprKind::Path(QPath::Resolved(_, path)) => (body_expr, path.span), + _ => return, }; - if let Some(return_expr_span) = return_expr_span { - span_lint_hir_and_then( - cx, - ASYNC_YIELDS_ASYNC, - body_expr.hir_id, - return_expr_span, - "an async construct yields a type which is itself awaitable", - |db| { - db.span_label(body_expr.span, "outer async construct"); - db.span_label(return_expr_span, "awaitable value not awaited"); - db.span_suggestion( - return_expr_span, - "consider awaiting this value", - format!("{}.await", snippet(cx, return_expr_span, "..")), - Applicability::MaybeIncorrect, - ); - }, - ); + + let return_expr_span = walk_span_to_context(return_expr_span, expr.span.ctxt()).unwrap_or(return_expr_span); + let mut applicability = Applicability::MaybeIncorrect; + let mut return_expr_snip = + Sugg::hir_with_context(cx, return_expr, expr.span.ctxt(), "..", &mut applicability); + if !is_expr_async_block(return_expr) { + return_expr_snip = return_expr_snip.maybe_paren(); } + + span_lint_hir_and_then( + cx, + ASYNC_YIELDS_ASYNC, + body_expr.hir_id, + return_expr_span, + "an async construct yields a type which is itself awaitable", + |db| { + db.span_label(body_expr.span, "outer async construct"); + db.span_label(return_expr_span, "awaitable value not awaited"); + db.span_suggestion( + return_expr_span, + "consider awaiting this value", + format!("{return_expr_snip}.await"), + applicability, + ); + }, + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/bool_comparison.rs b/src/tools/clippy/clippy_lints/src/bool_comparison.rs new file mode 100644 index 00000000000..722095909a6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/bool_comparison.rs @@ -0,0 +1,179 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_expn_of, peel_blocks, sym}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::source_map::Spanned; + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions of the form `x == true`, + /// `x != true` and order comparisons such as `x < true` (or vice versa) and + /// suggest using the variable directly. + /// + /// ### Why is this bad? + /// Unnecessary code. + /// + /// ### Example + /// ```rust,ignore + /// if x == true {} + /// if y == false {} + /// ``` + /// use `x` directly: + /// ```rust,ignore + /// if x {} + /// if !y {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub BOOL_COMPARISON, + complexity, + "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" +} + +declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); + +impl<'tcx> LateLintPass<'tcx> for BoolComparison { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node, .. }, left_side, right_side) = e.kind + && is_expn_of(left_side.span, sym::cfg).is_none() + && is_expn_of(right_side.span, sym::cfg).is_none() + && cx.typeck_results().expr_ty(left_side).is_bool() + && cx.typeck_results().expr_ty(right_side).is_bool() + { + let ignore_case = None::<(fn(_) -> _, &str)>; + let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; + match node { + BinOpKind::Eq => { + let true_case = Some((|h| h, "equality checks against true are unnecessary")); + let false_case = Some(( + |h: Sugg<'tcx>| !h, + "equality checks against false can be replaced by a negation", + )); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); + }, + BinOpKind::Ne => { + let true_case = Some(( + |h: Sugg<'tcx>| !h, + "inequality checks against true can be replaced by a negation", + )); + let false_case = Some((|h| h, "inequality checks against false are unnecessary")); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); + }, + BinOpKind::Lt => check_comparison( + cx, + e, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |h: Sugg<'tcx>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + Some(( + |l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r), + "order comparisons between booleans can be simplified", + )), + ), + BinOpKind::Gt => check_comparison( + cx, + e, + Some(( + |h: Sugg<'tcx>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)), + "order comparisons between booleans can be simplified", + )), + ), + _ => (), + } + } + } +} + +fn check_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, +) { + if let ExprKind::Binary(_, left_side, right_side) = e.kind { + let mut applicability = Applicability::MachineApplicable; + // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, + // calling `source_callsite` make sure macros are handled correctly, see issue #9907 + let binop_span = left_side.span.source_callsite().to(right_side.span.source_callsite()); + + match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { + (Some(true), None) => left_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(true)) => right_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (Some(false), None) => left_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(false)) => right_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (None, None) => no_literal.map_or((), |(h, m)| { + let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); + let right_side = Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + binop_span, + m, + "try", + h(left_side, right_side).into_string(), + applicability, + ); + }), + _ => (), + } + } +} + +fn suggest_bool_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + span: Span, + expr: &Expr<'_>, + mut app: Applicability, + message: &'static str, + conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, +) { + let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + span, + message, + "try", + conv_hint(hint).into_string(), + app, + ); +} + +fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> { + if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind + && let LitKind::Bool(value) = lit_ptr.node + { + return Some(value); + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs index a1543cabd2f..d78da9396fa 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -8,17 +8,12 @@ use rustc_middle::ty::{self, Ty}; use super::CAST_PTR_ALIGNMENT; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Cast(cast_expr, cast_to) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to) { - return; - } - let (cast_from, cast_to) = ( - cx.typeck_results().expr_ty(cast_expr), - cx.typeck_results().expr_ty(expr), - ); - lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); +} + +pub(super) fn check_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind && method_path.ident.name == sym::cast && let Some(generic_args) = method_path.args && let [GenericArg::Type(cast_to)] = generic_args.args @@ -74,14 +69,13 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => { if let ExprKind::Path(path) = &func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) && matches!( - cx.tcx.get_diagnostic_name(def_id), - Some( - sym::ptr_write_unaligned - | sym::ptr_read_unaligned - | sym::intrinsics_unaligned_volatile_load - | sym::intrinsics_unaligned_volatile_store - ) + name, + sym::ptr_write_unaligned + | sym::ptr_read_unaligned + | sym::intrinsics_unaligned_volatile_load + | sym::intrinsics_unaligned_volatile_store ) { true diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs index 46b0c88d3fe..bce7b4c69cc 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -1,10 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; +use clippy_utils::{get_parent_expr, is_no_std_crate}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; @@ -42,13 +44,52 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, let mut applicability = Applicability::MachineApplicable; let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + let krate = if is_no_std_crate(cx) { "core" } else { "std" }; span_lint_and_sugg( cx, CAST_SLICE_FROM_RAW_PARTS, span, format!("casting the result of `{func}` to {cast_to}"), "replace with", - format!("core::ptr::slice_{func}({ptr}, {len})"), + format!("{krate}::ptr::slice_{func}({ptr}, {len})"), + applicability, + ); + } +} + +/// Checks for implicit cast from slice reference to raw slice pointer. +pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Call(fun, [ptr_arg, len_arg]) = expr.peel_blocks().kind + && let ExprKind::Path(ref qpath) = fun.kind + && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() + && let Some(rpk) = raw_parts_kind(cx, fun_def_id) + && !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..))) + && let [deref, borrow] = cx.typeck_results().expr_adjustments(expr) + && matches!(deref.kind, Adjust::Deref(..)) + && let Adjustment { + kind: Adjust::Borrow(AutoBorrow::RawPtr(..)), + target, + } = borrow + && let ty::RawPtr(pointee_ty, _) = target.kind() + && pointee_ty.is_slice() + && !expr.span.from_expansion() + { + let func = match rpk { + RawPartsKind::Immutable => "from_raw_parts", + RawPartsKind::Mutable => "from_raw_parts_mut", + }; + let mut applicability = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); + let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; + let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + let krate = if is_no_std_crate(cx) { "core" } else { "std" }; + span_lint_and_sugg( + cx, + CAST_SLICE_FROM_RAW_PARTS, + expr.span, + format!("implicitly casting the result of `{func}` to `{target}`"), + "replace_with", + format!("{krate}::ptr::slice_{func}({ptr}, {len})"), applicability, ); } diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs index e25df9dd249..d2e62ee56e4 100644 --- a/src/tools/clippy/clippy_lints/src/casts/mod.rs +++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs @@ -873,7 +873,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to); cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv); + cast_ptr_alignment::check(cx, expr, cast_from, cast_to); ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); + ptr_as_ptr::check(cx, expr, cast_from_expr, cast_from, cast_to_hir, cast_to, self.msrv); as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); @@ -911,8 +913,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if self.msrv.meets(cx, msrvs::RAW_REF_OP) { borrow_as_ptr::check_implicit_cast(cx, expr); } - cast_ptr_alignment::check(cx, expr); - ptr_as_ptr::check(cx, expr, self.msrv); + if self.msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) { + cast_slice_from_raw_parts::check_implicit_cast(cx, expr); + } + cast_ptr_alignment::check_cast_method(cx, expr); cast_slice_different_sizes::check(cx, expr, self.msrv); ptr_cast_constness::check_null_ptr_cast_method(cx, expr); } diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs index 89075409098..d012380cb76 100644 --- a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs @@ -4,9 +4,9 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_span::{Span, sym}; use super::PTR_AS_PTR; @@ -26,13 +26,18 @@ impl OmitFollowedCastReason<'_> { } } -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) { - if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind - && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)) - && let ty::RawPtr(_, from_mutbl) = cast_from.kind() +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'tcx>, + cast_from_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to_hir: &hir::Ty<'_>, + cast_to: Ty<'tcx>, + msrv: Msrv, +) { + if let ty::RawPtr(_, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_pointee_ty, to_mutbl) = cast_to.kind() - && matches!((from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)) + && from_mutbl == to_mutbl // The `U` in `pointer::cast` have to be `Sized` // as explained here: https://github.com/rust-lang/rust/issues/60602. && to_pointee_ty.is_sized(cx.tcx, cx.typing_env()) @@ -40,7 +45,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) && !is_from_proc_macro(cx, expr) { let mut app = Applicability::MachineApplicable; - let turbofish = match &cast_to_hir_ty.kind { + let turbofish = match &cast_to_hir.kind { TyKind::Infer(()) => String::new(), TyKind::Ptr(mut_ty) => { if matches!(mut_ty.ty.kind, TyKind::Infer(())) { @@ -58,16 +63,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) // following `cast` does not compile because it fails to infer what type is expected // as type argument to `std::ptr::ptr_null` or `std::ptr::ptr_null_mut`, so // we omit following `cast`: - let omit_cast = if let ExprKind::Call(func, []) = cast_expr.kind + let omit_cast = if let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind && let Some(method_defid) = path.res.opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::ptr_null, method_defid) { - OmitFollowedCastReason::Null(qpath) - } else if cx.tcx.is_diagnostic_item(sym::ptr_null_mut, method_defid) { - OmitFollowedCastReason::NullMut(qpath) - } else { - OmitFollowedCastReason::None + match cx.tcx.get_diagnostic_name(method_defid) { + Some(sym::ptr_null) => OmitFollowedCastReason::Null(qpath), + Some(sym::ptr_null_mut) => OmitFollowedCastReason::NullMut(qpath), + _ => OmitFollowedCastReason::None, } } else { OmitFollowedCastReason::None @@ -78,7 +81,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app); ("try call directly", format!("{method}{turbofish}()")) } else { - let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app); + let cast_expr_sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app); ( "try `pointer::cast`, a safer alternative", diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs index c0c0a47f855..c0f13f5e578 100644 --- a/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs +++ b/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, QPath}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -12,26 +13,23 @@ use super::PTR_CAST_CONSTNESS; pub(super) fn check<'tcx>( cx: &LateContext<'_>, expr: &Expr<'_>, - cast_expr: &Expr<'_>, + cast_from_expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>, msrv: Msrv, ) { if let ty::RawPtr(from_ty, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_ty, to_mutbl) = cast_to.kind() - && matches!( - (from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not) - ) + && from_mutbl != to_mutbl && from_ty == to_ty && !from_ty.has_erased_regions() { - if let ExprKind::Call(func, []) = cast_expr.kind + if let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(QPath::Resolved(None, path)) = func.kind && let Some(defid) = path.res.opt_def_id() && let Some(prefix) = std_or_core(cx) && let mut app = Applicability::MachineApplicable - && let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app)) + && let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app) && let Some((_, after_lt)) = sugg.split_once("::<") && let Some((source, target, target_func)) = match cx.tcx.get_diagnostic_name(defid) { Some(sym::ptr_null) => Some(("const", "mutable", "null_mut")), @@ -53,11 +51,17 @@ pub(super) fn check<'tcx>( if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) { let mut app = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app); - let constness = match *to_mutbl { - Mutability::Not => "const", - Mutability::Mut => "mut", + let sugg = if let ExprKind::Cast(nested_from, nested_hir_ty) = cast_from_expr.kind + && let hir::TyKind::Ptr(ptr_ty) = nested_hir_ty.kind + && let hir::TyKind::Infer(()) = ptr_ty.ty.kind + { + // `(foo as *const _).cast_mut()` fails method name resolution + // avoid this by `as`-ing the full type + Sugg::hir_with_context(cx, nested_from, expr.span.ctxt(), "_", &mut app).as_ty(cast_from) + } else { + Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app) }; + let constness = to_mutbl.ptr_str(); span_lint_and_sugg( cx, @@ -73,8 +77,8 @@ pub(super) fn check<'tcx>( } pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::MethodCall(method, cast_expr, [], _) = expr.kind - && let ExprKind::Call(func, []) = cast_expr.kind + if let ExprKind::MethodCall(method, cast_from_expr, [], _) = expr.kind + && let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(QPath::Resolved(None, path)) = func.kind && let Some(defid) = path.res.opt_def_id() && let method = match (cx.tcx.get_diagnostic_name(defid), method.ident.name) { @@ -84,7 +88,7 @@ pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) } && let Some(prefix) = std_or_core(cx) && let mut app = Applicability::MachineApplicable - && let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app)) + && let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app) && let Some((_, after_lt)) = sugg.split_once("::<") { span_lint_and_sugg( diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs index 8f95c63a854..7646aa48b77 100644 --- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -23,8 +23,8 @@ declare_clippy_lint! { /// /// ### Known problems /// The true Cognitive Complexity of a method is not something we can - /// calculate using modern technology. This lint has been left in the - /// `nursery` so as to not mislead users into using this lint as a + /// calculate using modern technology. This lint has been left in + /// `restriction` so as to not mislead users into using this lint as a /// measurement tool. /// /// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793) diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index e67e8d9070f..d0c7443a4a4 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -35,6 +35,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO, crate::blocks_in_conditions::BLOCKS_IN_CONDITIONS_INFO, crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO, + crate::bool_comparison::BOOL_COMPARISON_INFO, crate::bool_to_int_with_if::BOOL_TO_INT_WITH_IF_INFO, crate::booleans::NONMINIMAL_BOOL_INFO, crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, @@ -538,7 +539,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::mutex_atomic::MUTEX_ATOMIC_INFO, crate::mutex_atomic::MUTEX_INTEGER_INFO, crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO, - crate::needless_bool::BOOL_COMPARISON_INFO, crate::needless_bool::NEEDLESS_BOOL_INFO, crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO, crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO, diff --git a/src/tools/clippy/clippy_lints/src/derivable_impls.rs b/src/tools/clippy/clippy_lints/src/derivable_impls.rs index 7580d6cab66..06c2393e0a3 100644 --- a/src/tools/clippy/clippy_lints/src/derivable_impls.rs +++ b/src/tools/clippy/clippy_lints/src/derivable_impls.rs @@ -10,7 +10,7 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, PointerCoercion}; -use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults}; +use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults, VariantDef}; use rustc_session::impl_lint_pass; use rustc_span::sym; @@ -85,6 +85,13 @@ fn contains_trait_object(ty: Ty<'_>) -> bool { } } +fn determine_derive_macro(cx: &LateContext<'_>, is_const: bool) -> Option<&'static str> { + (!is_const) + .then_some("derive") + .or_else(|| cx.tcx.features().enabled(sym::derive_const).then_some("derive_const")) +} + +#[expect(clippy::too_many_arguments)] fn check_struct<'tcx>( cx: &LateContext<'tcx>, item: &'tcx Item<'_>, @@ -93,6 +100,7 @@ fn check_struct<'tcx>( adt_def: AdtDef<'_>, ty_args: GenericArgsRef<'_>, typeck_results: &'tcx TypeckResults<'tcx>, + is_const: bool, ) { if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind && let Some(PathSegment { args, .. }) = p.segments.last() @@ -125,14 +133,18 @@ fn check_struct<'tcx>( ExprKind::Tup(fields) => fields.iter().all(is_default_without_adjusts), ExprKind::Call(callee, args) if is_path_self(callee) => args.iter().all(is_default_without_adjusts), ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_without_adjusts(ef.expr)), - _ => false, + _ => return, }; - if should_emit { + if should_emit && let Some(derive_snippet) = determine_derive_macro(cx, is_const) { let struct_span = cx.tcx.def_span(adt_def.did()); + let indent_enum = indent_of(cx, struct_span).unwrap_or(0); let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation - (struct_span.shrink_to_lo(), "#[derive(Default)]\n".to_string()), // Add the derive attribute + ( + struct_span.shrink_to_lo(), + format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)), + ), // Add the derive attribute ]; span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| { @@ -145,11 +157,41 @@ fn check_struct<'tcx>( } } -fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) { - if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind - && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res - && let variant_id = cx.tcx.parent(id) - && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) +fn extract_enum_variant<'tcx>( + cx: &LateContext<'tcx>, + func_expr: &'tcx Expr<'tcx>, + adt_def: AdtDef<'tcx>, +) -> Option<&'tcx VariantDef> { + match &peel_blocks(func_expr).kind { + ExprKind::Path(QPath::Resolved(None, p)) + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res + && let variant_id = cx.tcx.parent(id) + && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) => + { + Some(variant_def) + }, + ExprKind::Path(QPath::TypeRelative(ty, segment)) + if let TyKind::Path(QPath::Resolved(None, p)) = &ty.kind + && let Res::SelfTyAlias { + is_trait_impl: true, .. + } = p.res + && let variant_ident = segment.ident + && let Some(variant_def) = adt_def.variants().iter().find(|v| v.ident(cx.tcx) == variant_ident) => + { + Some(variant_def) + }, + _ => None, + } +} + +fn check_enum<'tcx>( + cx: &LateContext<'tcx>, + item: &'tcx Item<'tcx>, + func_expr: &'tcx Expr<'tcx>, + adt_def: AdtDef<'tcx>, + is_const: bool, +) { + if let Some(variant_def) = extract_enum_variant(cx, func_expr, adt_def) && variant_def.fields.is_empty() && !variant_def.is_field_list_non_exhaustive() { @@ -158,11 +200,15 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex let variant_span = cx.tcx.def_span(variant_def.def_id); let indent_variant = indent_of(cx, variant_span).unwrap_or(0); + let Some(derive_snippet) = determine_derive_macro(cx, is_const) else { + return; + }; + let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation ( enum_span.shrink_to_lo(), - format!("#[derive(Default)]\n{}", " ".repeat(indent_enum)), + format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)), ), // Add the derive attribute ( variant_span.shrink_to_lo(), @@ -201,10 +247,20 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { && !attrs.iter().any(|attr| attr.doc_str().is_some()) && cx.tcx.hir_attrs(impl_item_hir).is_empty() { + let is_const = of_trait.constness == hir::Constness::Const; if adt_def.is_struct() { - check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b)); + check_struct( + cx, + item, + self_ty, + func_expr, + adt_def, + args, + cx.tcx.typeck_body(*b), + is_const, + ); } else if adt_def.is_enum() && self.msrv.meets(cx, msrvs::DEFAULT_ENUM_ATTRIBUTE) { - check_enum(cx, item, func_expr, adt_def); + check_enum(cx, item, func_expr, adt_def, is_const); } } } diff --git a/src/tools/clippy/clippy_lints/src/entry.rs b/src/tools/clippy/clippy_lints/src/entry.rs index 182cb4e46d2..a5ec6777b43 100644 --- a/src/tools/clippy/clippy_lints/src/entry.rs +++ b/src/tools/clippy/clippy_lints/src/entry.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::is_copy; use clippy_utils::visitors::for_each_expr; @@ -194,7 +194,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { if let Some(sugg) = sugg { span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app); } else { - span_lint(cx, MAP_ENTRY, expr.span, lint_msg); + span_lint_and_help( + cx, + MAP_ENTRY, + expr.span, + lint_msg, + None, + format!( + "consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.{}.html#entry-api", + map_ty.name() + ), + ); } } } @@ -254,35 +264,28 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio _ => None, }); - match expr.kind { - ExprKind::MethodCall( - _, + if let ExprKind::MethodCall(_, map, [arg], _) = expr.kind + && let Expr { + kind: ExprKind::AddrOf(_, _, key), + span: key_span, + .. + } = arg + && key_span.eq_ctxt(expr.span) + { + let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; + let expr = ContainsExpr { + negated, map, - [ - Expr { - kind: ExprKind::AddrOf(_, _, key), - span: key_span, - .. - }, - ], - _, - ) if key_span.eq_ctxt(expr.span) => { - let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - let expr = ContainsExpr { - negated, - map, - key, - call_ctxt: expr.span.ctxt(), - }; - if cx.tcx.is_diagnostic_item(sym::btreemap_contains_key, id) { - Some((MapType::BTree, expr)) - } else if cx.tcx.is_diagnostic_item(sym::hashmap_contains_key, id) { - Some((MapType::Hash, expr)) - } else { - None - } - }, - _ => None, + key, + call_ctxt: expr.span.ctxt(), + }; + match cx.tcx.get_diagnostic_name(id) { + Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), + Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), + _ => None, + } + } else { + None } } @@ -311,7 +314,9 @@ struct InsertExpr<'tcx> { fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> { if let ExprKind::MethodCall(_, map, [key, value], _) = expr.kind { let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - if cx.tcx.is_diagnostic_item(sym::btreemap_insert, id) || cx.tcx.is_diagnostic_item(sym::hashmap_insert, id) { + if let Some(insert) = cx.tcx.get_diagnostic_name(id) + && matches!(insert, sym::btreemap_insert | sym::hashmap_insert) + { Some(InsertExpr { map, key, value }) } else { None diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 0eefc2f6109..2da1c2bad11 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -12,6 +12,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind, find_attr}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{ self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; @@ -148,10 +149,9 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx { return; } - let callee_ty_adjusted = typeck - .expr_adjustments(callee) - .last() - .map_or(callee_ty, |a| a.target.peel_refs()); + + let callee_ty_adjustments = typeck.expr_adjustments(callee); + let callee_ty_adjusted = callee_ty_adjustments.last().map_or(callee_ty, |a| a.target); let sig = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => { @@ -230,7 +230,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx }, _ => (), } + } else if let n_refs = + callee_ty_adjustments + .iter() + .rev() + .fold(0, |acc, adjustment| match adjustment.kind { + Adjust::Deref(Some(_)) => acc + 1, + Adjust::Deref(_) if acc > 0 => acc + 1, + _ => acc, + }) + && n_refs > 0 + { + snippet = format!("{}{snippet}", "*".repeat(n_refs)); } + let replace_with = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => cx.tcx.def_descr(*def), _ => "function", diff --git a/src/tools/clippy/clippy_lints/src/float_literal.rs b/src/tools/clippy/clippy_lints/src/float_literal.rs index ccaf38aee4d..6178addfff1 100644 --- a/src/tools/clippy/clippy_lints/src/float_literal.rs +++ b/src/tools/clippy/clippy_lints/src/float_literal.rs @@ -1,11 +1,12 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::numeric_literal; +use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FloatTy}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use std::fmt; declare_clippy_lint! { @@ -13,6 +14,8 @@ declare_clippy_lint! { /// Checks for float literals with a precision greater /// than that supported by the underlying type. /// + /// The lint is suppressed for literals with over `const_literal_digits_threshold` digits. + /// /// ### Why is this bad? /// Rust will truncate the literal silently. /// @@ -58,7 +61,21 @@ declare_clippy_lint! { "lossy whole number float literals" } -declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]); +pub struct FloatLiteral { + const_literal_digits_threshold: usize, +} + +impl_lint_pass!(FloatLiteral => [ + EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL +]); + +impl FloatLiteral { + pub fn new(conf: &'static Conf) -> Self { + Self { + const_literal_digits_threshold: conf.const_literal_digits_threshold, + } + } +} impl<'tcx> LateLintPass<'tcx> for FloatLiteral { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { @@ -126,13 +143,25 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { }, ); } - } else if digits > max as usize && count_digits(&float_str) < count_digits(sym_str) { + } else if digits > max as usize && count_digits(&float_str) < digits { + if digits >= self.const_literal_digits_threshold + && matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_)) + { + // If a big enough number of digits is specified and it's a constant + // we assume the user is definining a constant, and excessive precision is ok + return; + } span_lint_and_then( cx, EXCESSIVE_PRECISION, expr.span, "float has excessive precision", |diag| { + if digits >= self.const_literal_digits_threshold + && let Some(let_stmt) = maybe_let_stmt(cx, expr) + { + diag.span_note(let_stmt.span, "consider making it a `const` item"); + } diag.span_suggestion_verbose( expr.span, "consider changing the type or truncating it to", @@ -196,3 +225,11 @@ impl FloatFormat { } } } + +fn maybe_let_stmt<'a>(cx: &LateContext<'a>, expr: &hir::Expr<'_>) -> Option<&'a hir::LetStmt<'a>> { + let parent = cx.tcx.parent_hir_node(expr.hir_id); + match parent { + hir::Node::LetStmt(let_stmt) => Some(let_stmt), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/functions/mod.rs b/src/tools/clippy/clippy_lints/src/functions/mod.rs index ca5ea901814..5a40af42194 100644 --- a/src/tools/clippy/clippy_lints/src/functions/mod.rs +++ b/src/tools/clippy/clippy_lints/src/functions/mod.rs @@ -535,7 +535,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { def_id: LocalDefId, ) { let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); - too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold); + too_many_arguments::check_fn(cx, kind, decl, hir_id, def_id, self.too_many_arguments_threshold); too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold); not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); misnamed_getters::check_fn(cx, kind, decl, body, span); diff --git a/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs b/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs index 48d050aa36a..6c3c3d354ec 100644 --- a/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs +++ b/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs @@ -1,5 +1,7 @@ use rustc_abi::ExternAbi; -use rustc_hir::{self as hir, intravisit}; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; use rustc_lint::LateContext; use rustc_span::Span; @@ -10,39 +12,18 @@ use super::TOO_MANY_ARGUMENTS; pub(super) fn check_fn( cx: &LateContext<'_>, - kind: intravisit::FnKind<'_>, + kind: FnKind<'_>, decl: &hir::FnDecl<'_>, - span: Span, hir_id: hir::HirId, + def_id: LocalDefId, too_many_arguments_threshold: u64, ) { // don't warn for implementations, it's not their fault - if !is_trait_impl_item(cx, hir_id) { + if !is_trait_impl_item(cx, hir_id) // don't lint extern functions decls, it's not their fault either - match kind { - intravisit::FnKind::Method( - _, - &hir::FnSig { - header: hir::FnHeader { - abi: ExternAbi::Rust, .. - }, - .. - }, - ) - | intravisit::FnKind::ItemFn( - _, - _, - hir::FnHeader { - abi: ExternAbi::Rust, .. - }, - ) => check_arg_number( - cx, - decl, - span.with_hi(decl.output.span().hi()), - too_many_arguments_threshold, - ), - _ => {}, - } + && kind.header().is_some_and(|header| header.abi == ExternAbi::Rust) + { + check_arg_number(cx, decl, cx.tcx.def_span(def_id), too_many_arguments_threshold); } } 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 7158f9419c1..b50d91f1014 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,16 +2,16 @@ use clippy_config::Conf; 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::source::snippet_with_context; +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, is_res_lang_ctor, - path_res, peel_blocks, + path_res, peel_blocks, sym, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; declare_clippy_lint! { @@ -71,21 +71,21 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && 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 ctxt = expr.span.ctxt() - && then_expr.span.ctxt() == ctxt + && !expr.span.from_expansion() + && !then_expr.span.from_expansion() && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) - && !expr.span.in_external_macro(cx.sess().source_map()) && self.msrv.meets(cx, msrvs::BOOL_THEN) && !contains_return(then_block.stmts) { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { - "then_some" + sym::then_some } else { - "then" + sym::then }; + let ctxt = expr.span.ctxt(); span_lint_and_then( cx, @@ -98,16 +98,18 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { } let mut app = Applicability::MachineApplicable; - let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) + let cond_snip = Sugg::hir_with_context(cx, cond, ctxt, "[condition]", &mut app) .maybe_paren() .to_string(); let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; - let method_body = if let Some(first_stmt) = then_block.stmts.first() { - let (block_snippet, _) = - snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app); - let closure = if method_name == "then" { "|| " } else { "" }; - format!("{closure} {{ {block_snippet}; {arg_snip} }}") - } else if method_name == "then" { + let method_body = if let Some(first_stmt) = then_block.stmts.first() + && let Some(first_stmt_span) = walk_span_to_context(first_stmt.span, ctxt) + { + let block_snippet = + snippet_with_applicability(cx, first_stmt_span.until(then_expr.span), "..", &mut app); + let closure = if method_name == sym::then { "|| " } else { "" }; + format!("{closure} {{ {} {arg_snip} }}", block_snippet.trim_end()) + } else if method_name == sym::then { (std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned() } else { arg_snip.into_owned() diff --git a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs index 91f65d0b79c..13117f60abd 100644 --- a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs +++ b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; -use clippy_utils::ty; +use clippy_utils::{is_path_diagnostic_item, ty}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -107,8 +107,7 @@ impl LateLintPass<'_> for InstantSubtraction { fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { if let ExprKind::Call(fn_expr, []) = expr_block.kind - && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr) - && cx.tcx.is_diagnostic_item(sym::instant_now, fn_id) + && is_path_diagnostic_item(cx, fn_expr, sym::instant_now) { true } else { diff --git a/src/tools/clippy/clippy_lints/src/large_include_file.rs b/src/tools/clippy/clippy_lints/src/large_include_file.rs index 8707612fbdd..48ce1afc6e6 100644 --- a/src/tools/clippy/clippy_lints/src/large_include_file.rs +++ b/src/tools/clippy/clippy_lints/src/large_include_file.rs @@ -64,8 +64,8 @@ impl LateLintPass<'_> for LargeIncludeFile { } && len as u64 > self.max_file_size && let Some(macro_call) = root_macro_call_first_node(cx, expr) - && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) - || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) + && let Some(macro_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) + && matches!(macro_name, sym::include_bytes_macro | sym::include_str_macro) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index 57deb011f2b..f44a5fdf715 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -355,12 +355,15 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le return Some(LenOutput::Integral); } - if let Res::Def(_, def_id) = res { - if cx.tcx.is_diagnostic_item(sym::Option, def_id) && is_first_generic_integral(segment) { - return Some(LenOutput::Option(def_id)); - } else if cx.tcx.is_diagnostic_item(sym::Result, def_id) && is_first_generic_integral(segment) { - return Some(LenOutput::Result(def_id)); + if let Res::Def(_, def_id) = res + && let Some(res) = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::Option) => Some(LenOutput::Option(def_id)), + Some(sym::Result) => Some(LenOutput::Result(def_id)), + _ => None, } + && is_first_generic_integral(segment) + { + return Some(res); } return None; @@ -368,11 +371,10 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le match *sig.output().kind() { ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral), - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())) - }, - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())) + ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => Some(LenOutput::Option(adt.did())), + Some(sym::Result) => Some(LenOutput::Result(adt.did())), + _ => None, }, _ => None, } diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index c8594cf35e2..a89cf3fdc1e 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -84,6 +84,7 @@ mod attrs; mod await_holding_invalid; mod blocks_in_conditions; mod bool_assert_comparison; +mod bool_comparison; mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; @@ -474,10 +475,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co 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(|_| Box::new(float_literal::FloatLiteral)); + 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(needless_bool::BoolComparison)); + 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)); @@ -655,7 +656,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))); store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default()); store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); + store.register_late_pass(|_| Box::<unwrap_in_result::UnwrapInResult>::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(); diff --git a/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs b/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs index a71e6963f8c..74c0b178018 100644 --- a/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs @@ -4,7 +4,8 @@ use hir::intravisit::{Visitor, walk_expr}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir::{ - self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind, + self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnRetTy, + FnSig, Node, TyKind, }; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; @@ -73,7 +74,11 @@ fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> boo if let Node::Expr(Expr { kind: ExprKind::Closure(Closure { - kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), + kind: + ClosureKind::Coroutine(CoroutineKind::Desugared( + CoroutineDesugaring::Async, + CoroutineSource::Block | CoroutineSource::Closure, + )), .. }), .. diff --git a/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs b/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs index a9944d64ce2..8a2d0036203 100644 --- a/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs @@ -1,10 +1,10 @@ use super::MISSING_SPIN_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::std_or_core; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::sym; fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { @@ -39,8 +39,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &' ) = body.kind && let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind && [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name) - && let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind() - && cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did()) + && let callee_ty = cx.typeck_results().expr_ty(callee) + && is_type_diagnostic_item(cx, callee_ty, sym::AtomicBool) && let Some(std_or_core) = std_or_core(cx) { span_lint_and_sugg( diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 7bb684d65bb..11edb929d70 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::has_iter_method; use clippy_utils::visitors::is_local_used; -use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, sugg}; +use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, peel_hir_expr_while, sugg}; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::Applicability; @@ -253,12 +253,38 @@ struct VarVisitor<'a, 'tcx> { impl<'tcx> VarVisitor<'_, 'tcx> { fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { - let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); + let mut used_cnt = 0; + // It is `true` if all indices are direct + let mut index_used_directly = true; + + // Handle initial index + if is_local_used(self.cx, idx, self.var) { + used_cnt += 1; + index_used_directly &= matches!(idx.kind, ExprKind::Path(_)); + } + // Handle nested indices + let seqexpr = peel_hir_expr_while(seqexpr, |e| { + if let ExprKind::Index(e, idx, _) = e.kind { + if is_local_used(self.cx, idx, self.var) { + used_cnt += 1; + index_used_directly &= matches!(idx.kind, ExprKind::Path(_)); + } + Some(e) + } else { + None + } + }); + + match used_cnt { + 0 => return true, + n if n > 1 => self.nonindex = true, // Optimize code like `a[i][i]` + _ => {}, + } + if let ExprKind::Path(ref seqpath) = seqexpr.kind // the indexed container is referenced by a name && let QPath::Resolved(None, seqvar) = *seqpath && seqvar.segments.len() == 1 - && is_local_used(self.cx, idx, self.var) { if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name); @@ -312,7 +338,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> { impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind - // a range index op && let Some(trait_id) = self .cx .typeck_results() 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 51e21aa9734..13b93d2c009 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,12 +1,12 @@ use super::UNUSED_ENUMERATE_INDEX; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{pat_is_wild, sugg}; use rustc_errors::Applicability; use rustc_hir::def::DefKind; use rustc_hir::{Expr, ExprKind, Pat, PatKind}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::sym; /// Checks for the `UNUSED_ENUMERATE_INDEX` lint. @@ -17,8 +17,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_ && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind && let ty = cx.typeck_results().expr_ty(arg) && pat_is_wild(cx, &index.kind, body) - && let ty::Adt(base, _) = *ty.kind() - && cx.tcx.is_diagnostic_item(sym::Enumerate, base.did()) + && is_type_diagnostic_item(cx, ty, 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) { diff --git a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs index ac8c88f0205..2eebb2430fd 100644 --- a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs +++ b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs @@ -97,11 +97,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { return; } - if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) { - if let ExprKind::Match(recv, [arm, ..], _) = expr.kind { - let range = check_pat(&arm.pat.kind); - check_is_ascii(cx, macro_call.span, recv, &range, None); - } + let (arg, span, range) = if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) + && let ExprKind::Match(recv, [arm, ..], _) = expr.kind + { + let recv = peel_ref_operators(cx, recv); + let range = check_pat(&arm.pat.kind); + (recv, macro_call.span, range) } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind && path.ident.name == sym::contains && let Some(higher::Range { @@ -112,10 +113,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { && !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_)) { let arg = peel_ref_operators(cx, arg); - let ty_sugg = get_ty_sugg(cx, arg); let range = check_expr_range(start, end); - check_is_ascii(cx, expr.span, arg, &range, ty_sugg); - } + (arg, expr.span, range) + } else { + return; + }; + + let ty_sugg = get_ty_sugg(cx, arg); + check_is_ascii(cx, span, arg, &range, ty_sugg); } } @@ -146,9 +151,8 @@ fn check_is_ascii( CharRange::HexDigit => "is_ascii_hexdigit", CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => return, }; - let default_snip = ".."; let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_paren(); + let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), "_", &mut app).maybe_paren(); let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))]; if let Some((ty_span, ty)) = ty_sugg { suggestion.push((ty_span, format!("{recv}: {ty}"))); @@ -182,7 +186,7 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange { CharRange::Otherwise } }, - PatKind::Range(Some(start), Some(end), kind) if *kind == RangeEnd::Included => check_range(start, end), + PatKind::Range(Some(start), Some(end), RangeEnd::Included) => check_range(start, end), _ => CharRange::Otherwise, } } diff --git a/src/tools/clippy/clippy_lints/src/manual_retain.rs b/src/tools/clippy/clippy_lints/src/manual_retain.rs index 98e8b1f5cf9..7fb88763e64 100644 --- a/src/tools/clippy/clippy_lints/src/manual_retain.rs +++ b/src/tools/clippy/clippy_lints/src/manual_retain.rs @@ -123,8 +123,8 @@ fn check_iter( ) { if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::iter_copied, copied_def_id) - || cx.tcx.is_diagnostic_item(sym::iter_cloned, copied_def_id)) + && let Some(copied_name) = cx.tcx.get_diagnostic_name(copied_def_id) + && matches!(copied_name, sym::iter_copied | sym::iter_cloned) && let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) && cx.tcx.is_diagnostic_item(sym::iter_filter, filter_def_id) @@ -243,9 +243,9 @@ fn make_sugg( } fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { - ACCEPTABLE_METHODS - .iter() - .any(|&method| cx.tcx.is_diagnostic_item(method, collect_def_id)) + cx.tcx + .get_diagnostic_name(collect_def_id) + .is_some_and(|collect_name| ACCEPTABLE_METHODS.contains(&collect_name)) } fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/manual_strip.rs b/src/tools/clippy/clippy_lints/src/manual_strip.rs index 6bf43a1c6d4..07cce4046ca 100644 --- a/src/tools/clippy/clippy_lints/src/manual_strip.rs +++ b/src/tools/clippy/clippy_lints/src/manual_strip.rs @@ -75,12 +75,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { && let ExprKind::Path(target_path) = &target_arg.kind && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id) { - let strip_kind = if cx.tcx.is_diagnostic_item(sym::str_starts_with, method_def_id) { - StripKind::Prefix - } else if cx.tcx.is_diagnostic_item(sym::str_ends_with, method_def_id) { - StripKind::Suffix - } else { - return; + let strip_kind = match cx.tcx.get_diagnostic_name(method_def_id) { + Some(sym::str_starts_with) => StripKind::Prefix, + Some(sym::str_ends_with) => StripKind::Suffix, + _ => return, }; let target_res = cx.qpath_res(target_path, target_arg.hir_id); if target_res == Res::Err { diff --git a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs index af6a1b07a49..39e5289c62a 100644 --- a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs +++ b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs @@ -116,8 +116,10 @@ fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { /// The expression inside a closure may or may not have surrounding braces and /// semicolons, which causes problems when generating a suggestion. Given an /// expression that evaluates to '()' or '!', recursively remove useless braces -/// and semi-colons until is suitable for including in the suggestion template -fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Span> { +/// and semi-colons until is suitable for including in the suggestion template. +/// The `bool` is `true` when the resulting `span` needs to be enclosed in an +/// `unsafe` block. +fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<(Span, bool)> { if !is_unit_expression(cx, expr) { return None; } @@ -125,22 +127,24 @@ fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option< match expr.kind { hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(..) => { // Calls can't be reduced any more - Some(expr.span) + Some((expr.span, false)) }, hir::ExprKind::Block(block, _) => { + let is_unsafe = matches!(block.rules, hir::BlockCheckMode::UnsafeBlock(_)); match (block.stmts, block.expr.as_ref()) { ([], Some(inner_expr)) => { // If block only contains an expression, // reduce `{ X }` to `X` reduce_unit_expression(cx, inner_expr) + .map(|(span, inner_is_unsafe)| (span, inner_is_unsafe || is_unsafe)) }, ([inner_stmt], None) => { // If block only contains statements, // reduce `{ X; }` to `X` or `X;` match inner_stmt.kind { - hir::StmtKind::Let(local) => Some(local.span), - hir::StmtKind::Expr(e) => Some(e.span), - hir::StmtKind::Semi(..) => Some(inner_stmt.span), + hir::StmtKind::Let(local) => Some((local.span, is_unsafe)), + hir::StmtKind::Expr(e) => Some((e.span, is_unsafe)), + hir::StmtKind::Semi(..) => Some((inner_stmt.span, is_unsafe)), hir::StmtKind::Item(..) => None, } }, @@ -228,10 +232,11 @@ fn lint_map_unit_fn( let msg = suggestion_msg("closure", map_type); span_lint_and_then(cx, lint, expr.span, msg, |diag| { - if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) { + if let Some((reduced_expr_span, is_unsafe)) = reduce_unit_expression(cx, closure_expr) { let mut applicability = Applicability::MachineApplicable; + let (prefix_is_unsafe, suffix_is_unsafe) = if is_unsafe { ("unsafe { ", " }") } else { ("", "") }; let suggestion = format!( - "if let {0}({1}) = {2} {{ {3} }}", + "if let {0}({1}) = {2} {{ {prefix_is_unsafe}{3}{suffix_is_unsafe} }}", variant, snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability), snippet_with_applicability(cx, var_arg.span, "_", &mut applicability), diff --git a/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs index 5b50efad3e4..aaf559fc443 100644 --- a/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs @@ -4,20 +4,22 @@ use clippy_utils::msrvs::Msrv; use clippy_utils::source::snippet; use clippy_utils::visitors::is_local_used; use clippy_utils::{ - SpanlessEq, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, + SpanlessEq, get_ref_operators, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, + peel_ref_operators, }; +use rustc_ast::BorrowKind; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, HirId, Pat, PatExpr, PatExprKind, PatKind}; +use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Span; use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or}; -pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: Msrv) { +pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) { if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) { for arm in arms { - check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body), msrv); + check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv); } } } @@ -27,15 +29,18 @@ pub(super) fn check_if_let<'tcx>( pat: &'tcx Pat<'_>, body: &'tcx Expr<'_>, else_expr: Option<&'tcx Expr<'_>>, + let_expr: &'tcx Expr<'_>, msrv: Msrv, ) { - check_arm(cx, false, pat, body, None, else_expr, msrv); + check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv); } +#[allow(clippy::too_many_arguments)] fn check_arm<'tcx>( cx: &LateContext<'tcx>, outer_is_match: bool, outer_pat: &'tcx Pat<'tcx>, + outer_cond: &'tcx Expr<'tcx>, outer_then_body: &'tcx Expr<'tcx>, outer_guard: Option<&'tcx Expr<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, @@ -82,6 +87,9 @@ fn check_arm<'tcx>( }, IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)), } + // Check if the inner expression contains any borrows/dereferences + && let ref_types = get_ref_operators(cx, inner_scrutinee) + && let Some(method) = build_ref_method_chain(ref_types) { let msg = format!( "this `{}` can be collapsed into the outer `{}`", @@ -103,6 +111,10 @@ fn check_arm<'tcx>( let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]); help_span.push_span_label(binding_span, "replace this binding"); help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}")); + if !method.is_empty() { + let outer_cond_msg = format!("use: `{}{}`", snippet(cx, outer_cond.span, ".."), method); + help_span.push_span_label(outer_cond.span, outer_cond_msg); + } diag.span_help( help_span, "the outer pattern can be modified to include the inner pattern", @@ -148,3 +160,30 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi }); (span, is_innermost_parent_pat_struct) } + +/// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`, +/// `.copied()`) based on reference operators +fn build_ref_method_chain(expr: Vec<&Expr<'_>>) -> Option<String> { + let mut req_method_calls = String::new(); + + for ref_operator in expr { + match ref_operator.kind { + ExprKind::AddrOf(BorrowKind::Raw, _, _) => { + return None; + }, + ExprKind::AddrOf(_, m, _) if m.is_mut() => { + req_method_calls.push_str(".as_mut()"); + }, + ExprKind::AddrOf(_, _, _) => { + req_method_calls.push_str(".as_ref()"); + }, + // Deref operator is the only operator that this function should have received + ExprKind::Unary(_, _) => { + req_method_calls.push_str(".copied()"); + }, + _ => (), + } + } + + Some(req_method_calls) +} diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs index c128fc40b73..6f49c552411 100644 --- a/src/tools/clippy/clippy_lints/src/matches/mod.rs +++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs @@ -1073,7 +1073,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source); } - collapsible_match::check_match(cx, arms, self.msrv); + collapsible_match::check_match(cx, ex, arms, self.msrv); if !from_expansion { // These don't depend on a relationship between multiple arms match_wild_err_arm::check(cx, ex, arms); @@ -1137,7 +1137,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); } } else if let Some(if_let) = higher::IfLet::hir(cx, expr) { - collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, self.msrv); + collapsible_match::check_if_let( + cx, + if_let.let_pat, + if_let.if_then, + if_let.if_else, + if_let.let_expr, + self.msrv, + ); significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else); if !from_expansion { if let Some(else_expr) = if_let.if_else { 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 027dd7ce053..81fecc87256 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 @@ -226,11 +226,12 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] enum SigDropHolder { /// No values with significant drop present in this expression. /// /// Expressions that we've emitted lints do not count. + #[default] None, /// Some field in this expression references to values with significant drop. /// @@ -244,12 +245,6 @@ enum SigDropHolder { Moved, } -impl Default for SigDropHolder { - fn default() -> Self { - Self::None - } -} - struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, parent_expr: Option<&'tcx Expr<'tcx>>, diff --git a/src/tools/clippy/clippy_lints/src/matches/try_err.rs b/src/tools/clippy/clippy_lints/src/matches/try_err.rs index ff7769af1df..af90cb5e673 100644 --- a/src/tools/clippy/clippy_lints/src/matches/try_err.rs +++ b/src/tools/clippy/clippy_lints/src/matches/try_err.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::option_arg_ty; use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res}; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; @@ -28,25 +28,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine && is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr) && let Some(return_ty) = find_return_type(cx, &expr.kind) { - let prefix; - let suffix; - let err_ty; - - if let Some(ty) = result_error_type(cx, return_ty) { - prefix = "Err("; - suffix = ")"; - err_ty = ty; + let (prefix, suffix, err_ty) = if let Some(ty) = result_error_type(cx, return_ty) { + ("Err(", ")", ty) } else if let Some(ty) = poll_result_error_type(cx, return_ty) { - prefix = "Poll::Ready(Err("; - suffix = "))"; - err_ty = ty; + ("Poll::Ready(Err(", "))", ty) } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) { - prefix = "Poll::Ready(Some(Err("; - suffix = ")))"; - err_ty = ty; + ("Poll::Ready(Some(Err(", ")))", ty) } else { return; - } + }; span_lint_and_then( cx, @@ -88,8 +78,8 @@ fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> O /// Extracts the error type from Result<T, E>. fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { - if let ty::Adt(_, subst) = ty.kind() - && is_type_diagnostic_item(cx, ty, sym::Result) + if let ty::Adt(def, subst) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::Result, def.did()) { Some(subst.type_at(1)) } else { @@ -101,11 +91,9 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { if let ty::Adt(def, subst) = ty.kind() && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) - && let ready_ty = subst.type_at(0) - && let ty::Adt(ready_def, ready_subst) = ready_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Result, ready_def.did()) { - Some(ready_subst.type_at(1)) + let ready_ty = subst.type_at(0); + result_error_type(cx, ready_ty) } else { None } @@ -116,13 +104,9 @@ fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> if let ty::Adt(def, subst) = ty.kind() && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) && let ready_ty = subst.type_at(0) - && let ty::Adt(ready_def, ready_subst) = ready_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Option, ready_def.did()) - && let some_ty = ready_subst.type_at(0) - && let ty::Adt(some_def, some_subst) = some_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Result, some_def.did()) + && let Some(some_ty) = option_arg_ty(cx, ready_ty) { - Some(some_subst.type_at(1)) + result_error_type(cx, some_ty) } else { None } diff --git a/src/tools/clippy/clippy_lints/src/mem_replace.rs b/src/tools/clippy/clippy_lints/src/mem_replace.rs index 28efd2038b3..e39916f733d 100644 --- a/src/tools/clippy/clippy_lints/src/mem_replace.rs +++ b/src/tools/clippy/clippy_lints/src/mem_replace.rs @@ -215,7 +215,8 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<' && let ExprKind::Path(ref repl_func_qpath) = repl_func.kind && let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) { + let repl_name = cx.tcx.get_diagnostic_name(repl_def_id); + if repl_name == Some(sym::mem_uninitialized) { let Some(top_crate) = std_or_core(cx) else { return }; let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( @@ -230,9 +231,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<' ), applicability, ); - } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) - && !cx.typeck_results().expr_ty(src).is_primitive() - { + } else if repl_name == Some(sym::mem_zeroed) && !cx.typeck_results().expr_ty(src).is_primitive() { span_lint_and_help( cx, MEM_REPLACE_WITH_UNINIT, diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs index 96e2de0dc1c..65583c6a981 100644 --- a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -24,9 +24,10 @@ pub(super) fn check( && let Some(name) = cx.tcx.get_diagnostic_name(adt.did()) { let caller_type = match name { - sym::Rc => "Rc", - sym::Arc => "Arc", - sym::RcWeak | sym::ArcWeak => "Weak", + sym::Rc => "std::rc::Rc", + sym::Arc => "std::sync::Arc", + sym::RcWeak => "std::rc::Weak", + sym::ArcWeak => "std::sync::Weak", _ => return, }; span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs index 5b8457bdd16..2da0f8341b1 100644 --- a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs @@ -233,18 +233,16 @@ impl<'tcx> OffendingFilterExpr<'tcx> { // the latter only calls `effect` once let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); - if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym::is_some { - Some(Self::IsSome { + match (cx.tcx.get_diagnostic_name(recv_ty.did()), path.ident.name) { + (Some(sym::Option), sym::is_some) => Some(Self::IsSome { receiver, side_effect_expr_span, - }) - } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym::is_ok { - Some(Self::IsOk { + }), + (Some(sym::Result), sym::is_ok) => Some(Self::IsOk { receiver, side_effect_expr_span, - }) - } else { - None + }), + _ => None, } } else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some() // we know for a fact that the wildcard pattern is the second arm diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs b/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs index 9a62b719a8f..fa8f9d640ee 100644 --- a/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -24,32 +24,29 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else { return None; }; - let did = adt.did(); - if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { - // For array::IntoIter<T, const N: usize>, the length is the second generic - // parameter. - substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) - } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) - && let ExprKind::MethodCall(_, recv, ..) = iter.kind - { - if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { - // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) - len.try_to_target_usize(cx.tcx).map(u128::from) - } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { - match args { - VecArgs::Vec(vec) => vec.len().try_into().ok(), - VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::ArrayIntoIter) => { + // For array::IntoIter<T, const N: usize>, the length is the second generic + // parameter. + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) + }, + Some(sym::SliceIter) if let ExprKind::MethodCall(_, recv, ..) = iter.kind => { + if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { + // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) + len.try_to_target_usize(cx.tcx).map(u128::from) + } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { + match args { + VecArgs::Vec(vec) => vec.len().try_into().ok(), + VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + } + } else { + None } - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::IterEmpty, did) { - Some(0) - } else if cx.tcx.is_diagnostic_item(sym::IterOnce, did) { - Some(1) - } else { - None + }, + Some(sym::IterEmpty) => Some(0), + Some(sym::IterOnce) => Some(1), + _ => None, } } diff --git a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs index f7bb8c1d696..750f933330a 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs @@ -50,10 +50,10 @@ fn try_get_caller_ty_name_and_method_name( } } else { if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { - return Some(("Option", "and_then")); - } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { - return Some(("Result", "and_then")); + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => return Some(("Option", "and_then")), + Some(sym::Result) => return Some(("Result", "and_then")), + _ => {}, } } None diff --git a/src/tools/clippy/clippy_lints/src/methods/map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/map_identity.rs index 98def66ca14..a98cfff8bfb 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_identity.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_identity.rs @@ -1,14 +1,16 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_expr_untyped_identity_function, is_trait_method, path_to_local}; -use rustc_ast::BindingMode; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::{is_expr_untyped_identity_function, is_mutable, is_trait_method, path_to_local_with_projections}; use rustc_errors::Applicability; -use rustc_hir::{self as hir, Node, PatKind}; -use rustc_lint::LateContext; +use rustc_hir::{self as hir, ExprKind, Node, PatKind}; +use rustc_lint::{LateContext, LintContext}; use rustc_span::{Span, Symbol, sym}; use super::MAP_IDENTITY; +const MSG: &str = "unnecessary map of the identity function"; + pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -23,26 +25,70 @@ pub(super) fn check( || is_type_diagnostic_item(cx, caller_ty, sym::Result) || is_type_diagnostic_item(cx, caller_ty, sym::Option)) && is_expr_untyped_identity_function(cx, map_arg) - && let Some(sugg_span) = expr.span.trim_start(caller.span) + && let Some(call_span) = expr.span.trim_start(caller.span) { - // If the result of `.map(identity)` is used as a mutable reference, - // the caller must not be an immutable binding. - if cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr() - && let Some(hir_id) = path_to_local(caller) - && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) - && !matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) - { - return; - } + let main_sugg = (call_span, String::new()); + let mut app = if is_copy(cx, caller_ty) { + // there is technically a behavioral change here for `Copy` iterators, where + // `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and + // changing it to `iter.next()` mutates iter directly + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }; + + let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr(); + if needs_to_be_mutable && !is_mutable(cx, caller) { + if let Some(hir_id) = path_to_local_with_projections(caller) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + && let PatKind::Binding(_, _, ident, _) = pat.kind + { + // We can reach the binding -- suggest making it mutable + let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))]; + + let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app); - span_lint_and_sugg( - cx, - MAP_IDENTITY, - sugg_span, - "unnecessary map of the identity function", - format!("remove the call to `{name}`"), - String::new(), - Applicability::MachineApplicable, - ); + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + diag.multipart_suggestion( + format!("remove the call to `{name}`, and make `{ident}` mutable"), + suggs, + app, + ); + }); + } else { + // If we can't make the binding mutable, prevent the suggestion from being automatically applied, + // and add a complementary help message. + app = Applicability::Unspecified; + + let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) + && let ExprKind::MethodCall(method, ..) = expr.kind + { + Some(method.ident) + } else { + None + }; + + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app); + + let note = if let Some(method_requiring_mut) = method_requiring_mut { + format!("this must be made mutable to use `{method_requiring_mut}`") + } else { + "this must be made mutable".to_string() + }; + diag.span_note(caller.span, note); + }); + } + } else { + span_lint_and_sugg( + cx, + MAP_IDENTITY, + main_sugg.0, + MSG, + format!("remove the call to `{name}`"), + main_sugg.1, + app, + ); + } } } diff --git a/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs index 63ee922acfa..906ead16fd0 100644 --- a/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs @@ -38,17 +38,13 @@ pub(super) fn check( ]; let is_deref = match map_arg.kind { - hir::ExprKind::Path(ref expr_qpath) => { - cx.qpath_res(expr_qpath, map_arg.hir_id) - .opt_def_id() - .is_some_and(|fun_def_id| { - cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, fun_def_id) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, fun_def_id)) - }) - }, + hir::ExprKind::Path(ref expr_qpath) => cx + .qpath_res(expr_qpath, map_arg.hir_id) + .opt_def_id() + .and_then(|fun_def_id| cx.tcx.get_diagnostic_name(fun_def_id)) + .is_some_and(|fun_name| { + matches!(fun_name, sym::deref_method | sym::deref_mut_method) || deref_aliases.contains(&fun_name) + }), hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let closure_body = cx.tcx.hir_body(body); let closure_expr = peel_blocks(closure_body.value); @@ -63,13 +59,11 @@ pub(super) fn check( .map(|x| &x.kind) .collect::<Box<[_]>>() && let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj + && let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap() + && let Some(method_name) = cx.tcx.get_diagnostic_name(method_did) { - let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); - cx.tcx.is_diagnostic_item(sym::deref_method, method_did) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, method_did) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, method_did)) + matches!(method_name, sym::deref_method | sym::deref_mut_method) + || deref_aliases.contains(&method_name) } else { false } diff --git a/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs index 3e64e15dc86..1a760ea733d 100644 --- a/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs @@ -62,7 +62,7 @@ fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: Lang if let ExprKind::Call(some_expr, [arg]) = expr.kind && is_res_lang_ctor(cx, path_res(cx, some_expr), item) { - Some(arg.span) + Some(arg.span.source_callsite()) } else { None } diff --git a/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs b/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs index 407f2e80aff..6738bbf0a12 100644 --- a/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs +++ b/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs @@ -31,8 +31,8 @@ fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool { } pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if let Some(recv_adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() - && cx.tcx.is_diagnostic_item(sym::Stdin, recv_adt.did()) + let recv_ty = cx.typeck_results().expr_ty(recv); + if is_type_diagnostic_item(cx, recv_ty, sym::Stdin) && let ExprKind::Path(QPath::Resolved(_, path)) = arg.peel_borrows().kind && let Res::Local(local_id) = path.res { diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs index ccdf5529d53..ef3d7acdc01 100644 --- a/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs +++ b/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs @@ -5,10 +5,10 @@ use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) { - single_char_push_string::check(cx, expr, receiver, args); - } else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) { - single_char_insert_string::check(cx, expr, receiver, args); + match cx.tcx.get_diagnostic_name(fn_def_id) { + Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args), + Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args), + _ => {}, } } } diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs index ce7aefed01f..ffc237e3c24 100644 --- a/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_diag_trait_item; use clippy_utils::source::snippet_with_context; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_middle::ty::{self}; use rustc_span::sym; use super::SUSPICIOUS_TO_OWNED; @@ -14,8 +14,7 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && is_diag_trait_item(cx, method_def_id, sym::ToOwned) && let input_type = cx.typeck_results().expr_ty(expr) - && let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind() - && cx.tcx.is_diagnostic_item(sym::Cow, adt.did()) + && is_type_diagnostic_item(cx, input_type, sym::Cow) { let mut app = Applicability::MaybeIncorrect; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs index 413881d5ec9..b87d81b7102 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -22,7 +22,8 @@ pub(super) fn check<'tcx>( let typeck_results = cx.typeck_results(); let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), typeck_results); if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id)) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(id) + && matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max) { if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv) && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg) 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 index af466fe091c..af4ade3cc0f 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::AdtDef; use rustc_span::{Span, sym}; use crate::loops::UNUSED_ENUMERATE_INDEX; @@ -39,9 +39,8 @@ use crate::loops::UNUSED_ENUMERATE_INDEX; /// * `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 let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) - // If we call a method on a `std::iter::Enumerate` instance - && cx.tcx.is_diagnostic_item(sym::Enumerate, recv_ty_defid) + // If we call a method on a `std::iter::Enumerate` instance + if is_type_diagnostic_item(cx, recv_ty, sym::Enumerate) // If we are calling a method of the `Iterator` trait && is_trait_method(cx, call_expr, sym::Iterator) // And the map argument is a closure diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs index d02952eb487..28555a61090 100644 --- a/src/tools/clippy/clippy_lints/src/missing_inline.rs +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_hir}; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; @@ -64,14 +64,20 @@ declare_clippy_lint! { "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" } -fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[Attribute], sp: Span, desc: &'static str) { +fn check_missing_inline_attrs( + cx: &LateContext<'_>, + attrs: &[Attribute], + sp: Span, + desc: &'static str, + hir_id: Option<hir::HirId>, +) { if !find_attr!(attrs, AttributeKind::Inline(..)) { - span_lint( - cx, - MISSING_INLINE_IN_PUBLIC_ITEMS, - sp, - format!("missing `#[inline]` for {desc}"), - ); + let msg = format!("missing `#[inline]` for {desc}"); + if let Some(hir_id) = hir_id { + span_lint_hir(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, hir_id, sp, msg); + } else { + span_lint(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, sp, msg); + } } } @@ -103,17 +109,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let desc = "a function"; let attrs = cx.tcx.hir_attrs(it.hir_id()); - check_missing_inline_attrs(cx, attrs, it.span, desc); + check_missing_inline_attrs(cx, attrs, it.span, desc, None); }, - hir::ItemKind::Trait( - ref _constness, - ref _is_auto, - ref _unsafe, - _ident, - _generics, - _bounds, - trait_items, - ) => { + hir::ItemKind::Trait(.., trait_items) => { // note: we need to check if the trait is exported so we can't use // `LateLintPass::check_trait_item` here. for &tit in trait_items { @@ -127,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let desc = "a default trait method"; let item = cx.tcx.hir_trait_item(tit); let attrs = cx.tcx.hir_attrs(item.hir_id()); - check_missing_inline_attrs(cx, attrs, item.span, desc); + check_missing_inline_attrs(cx, attrs, item.span, desc, Some(tit.hir_id())); } }, } @@ -182,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } let attrs = cx.tcx.hir_attrs(impl_item.hir_id()); - check_missing_inline_attrs(cx, attrs, impl_item.span, desc); + check_missing_inline_attrs(cx, attrs, impl_item.span, desc, None); } } diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs index 31f51b45754..ec93ef97cfa 100644 --- a/src/tools/clippy/clippy_lints/src/mut_reference.rs +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -1,4 +1,6 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; @@ -83,13 +85,18 @@ fn check_arguments<'tcx>( let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); for (argument, parameter) in iter::zip(arguments, parameters) { if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind { - span_lint( + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr(); + span_lint_and_sugg( cx, UNNECESSARY_MUT_PASSED, argument.span, format!("the {fn_kind} `{name}` doesn't need a mutable reference"), + "remove this `mut`", + sugg.to_string(), + applicability, ); } } diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs index 6ae26156bc4..854e927aa2f 100644 --- a/src/tools/clippy/clippy_lints/src/needless_bool.rs +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -2,16 +2,14 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{ - SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, - is_receiver_of_method_call, peel_blocks, peel_blocks_with_stmt, span_contains_comment, sym, + SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call, + peel_blocks, peel_blocks_with_stmt, span_contains_comment, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does @@ -52,31 +50,6 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for expressions of the form `x == true`, - /// `x != true` and order comparisons such as `x < true` (or vice versa) and - /// suggest using the variable directly. - /// - /// ### Why is this bad? - /// Unnecessary code. - /// - /// ### Example - /// ```rust,ignore - /// if x == true {} - /// if y == false {} - /// ``` - /// use `x` directly: - /// ```rust,ignore - /// if x {} - /// if !y {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub BOOL_COMPARISON, - complexity, - "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" -} - -declare_clippy_lint! { - /// ### What it does /// Checks for expressions of the form `if c { x = true } else { x = false }` /// (or vice versa) and suggest assigning the variable directly from the /// condition. @@ -224,201 +197,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { } } -declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); - -impl<'tcx> LateLintPass<'tcx> for BoolComparison { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if e.span.from_expansion() { - return; - } - - if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind { - let ignore_case = None::<(fn(_) -> _, &str)>; - let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; - match node { - BinOpKind::Eq => { - let true_case = Some((|h| h, "equality checks against true are unnecessary")); - let false_case = Some(( - |h: Sugg<'tcx>| !h, - "equality checks against false can be replaced by a negation", - )); - check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); - }, - BinOpKind::Ne => { - let true_case = Some(( - |h: Sugg<'tcx>| !h, - "inequality checks against true can be replaced by a negation", - )); - let false_case = Some((|h| h, "inequality checks against false are unnecessary")); - check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); - }, - BinOpKind::Lt => check_comparison( - cx, - e, - ignore_case, - Some((|h| h, "greater than checks against false are unnecessary")), - Some(( - |h: Sugg<'tcx>| !h, - "less than comparison against true can be replaced by a negation", - )), - ignore_case, - Some(( - |l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r), - "order comparisons between booleans can be simplified", - )), - ), - BinOpKind::Gt => check_comparison( - cx, - e, - Some(( - |h: Sugg<'tcx>| !h, - "less than comparison against true can be replaced by a negation", - )), - ignore_case, - ignore_case, - Some((|h| h, "greater than checks against false are unnecessary")), - Some(( - |l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)), - "order comparisons between booleans can be simplified", - )), - ), - _ => (), - } - } - } -} - -struct ExpressionInfoWithSpan { - one_side_is_unary_not: bool, - left_span: Span, - right_span: Span, -} - -fn is_unary_not(e: &Expr<'_>) -> (bool, Span) { - if let ExprKind::Unary(UnOp::Not, operand) = e.kind { - return (true, operand.span); - } - (false, e.span) -} - -fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan { - let left = is_unary_not(left_side); - let right = is_unary_not(right_side); - - ExpressionInfoWithSpan { - one_side_is_unary_not: left.0 != right.0, - left_span: left.1, - right_span: right.1, - } -} - -fn check_comparison<'a, 'tcx>( - cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, - left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, -) { - if let ExprKind::Binary(op, left_side, right_side) = e.kind { - let (l_ty, r_ty) = ( - cx.typeck_results().expr_ty(left_side), - cx.typeck_results().expr_ty(right_side), - ); - if is_expn_of(left_side.span, sym::cfg).is_some() || is_expn_of(right_side.span, sym::cfg).is_some() { - return; - } - if l_ty.is_bool() && r_ty.is_bool() { - let mut applicability = Applicability::MachineApplicable; - // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, - // calling `source_callsite` make sure macros are handled correctly, see issue #9907 - let binop_span = left_side - .span - .source_callsite() - .with_hi(right_side.span.source_callsite().hi()); - - if op.node == BinOpKind::Eq { - let expression_info = one_side_is_unary_not(left_side, right_side); - if expression_info.one_side_is_unary_not { - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - "this comparison might be written more concisely", - "try simplifying it as shown", - format!( - "{} != {}", - snippet_with_applicability( - cx, - expression_info.left_span.source_callsite(), - "..", - &mut applicability - ), - snippet_with_applicability( - cx, - expression_info.right_span.source_callsite(), - "..", - &mut applicability - ) - ), - applicability, - ); - } - } - - match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { - (Some(true), None) => left_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(true)) => right_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (Some(false), None) => left_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(false)) => right_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (None, None) => no_literal.map_or((), |(h, m)| { - let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); - let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - m, - "try simplifying it as shown", - h(left_side, right_side).into_string(), - applicability, - ); - }), - _ => (), - } - } - } -} - -fn suggest_bool_comparison<'a, 'tcx>( - cx: &LateContext<'tcx>, - span: Span, - expr: &Expr<'_>, - mut app: Applicability, - message: &'static str, - conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, -) { - let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - span, - message, - "try simplifying it as shown", - conv_hint(hint).into_string(), - app, - ); -} - enum Expression { Bool(bool), RetBool(bool), diff --git a/src/tools/clippy/clippy_lints/src/needless_for_each.rs b/src/tools/clippy/clippy_lints/src/needless_for_each.rs index a67545e419c..3a6ccc2bca9 100644 --- a/src/tools/clippy/clippy_lints/src/needless_for_each.rs +++ b/src/tools/clippy/clippy_lints/src/needless_for_each.rs @@ -1,6 +1,6 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind}; +use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -70,12 +70,24 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { && has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some() // Skip the lint if the body is not block because this is simpler than `for` loop. // e.g. `v.iter().for_each(f)` is simpler and clearer than using `for` loop. - && let ExprKind::Closure(&Closure { body, .. }) = for_each_arg.kind + && let ExprKind::Closure(&Closure { body, fn_decl, .. }) = for_each_arg.kind && let body = cx.tcx.hir_body(body) // Skip the lint if the body is not safe, so as not to suggest `for … in … unsafe {}` // and suggesting `for … in … { unsafe { } }` is a little ugly. && !matches!(body.value.kind, ExprKind::Block(Block { rules: BlockCheckMode::UnsafeBlock(_), .. }, ..)) { + let mut applicability = Applicability::MachineApplicable; + + // If any closure parameter has an explicit type specified, applying the lint would necessarily + // remove that specification, possibly breaking type inference + if fn_decl + .inputs + .iter() + .any(|input| matches!(input.kind, TyKind::Infer(..))) + { + applicability = Applicability::MaybeIncorrect; + } + let mut ret_collector = RetCollector::default(); ret_collector.visit_expr(body.value); @@ -84,18 +96,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { return; } - let (mut applicability, ret_suggs) = if ret_collector.spans.is_empty() { - (Applicability::MachineApplicable, None) + let ret_suggs = if ret_collector.spans.is_empty() { + None } else { - ( - Applicability::MaybeIncorrect, - Some( - ret_collector - .spans - .into_iter() - .map(|span| (span, "continue".to_string())) - .collect(), - ), + applicability = Applicability::MaybeIncorrect; + Some( + ret_collector + .spans + .into_iter() + .map(|span| (span, "continue".to_string())) + .collect(), ) }; 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 04b09276966..ba67dc62abb 100644 --- a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs +++ b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs @@ -134,7 +134,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { return; } - if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id) + let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id); + if trait_name == Some(sym::Clone) && let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) && implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[]) { @@ -170,12 +171,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { String::new(), Applicability::MaybeIncorrect, ); - - return; } - } - - if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id) + } else if trait_name == Some(sym::PartialOrd) && impl_item.ident.name == sym::partial_cmp && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) diff --git a/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs b/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs index 23a1622f30f..cb934466bd8 100644 --- a/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs +++ b/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,13 +43,11 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym::mode - && matches!( - cx.tcx.get_diagnostic_name(adt.did()), - Some(sym::FsOpenOptions | sym::DirBuilder) - )) - || (path.ident.name == sym::set_mode - && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) + && matches!( + (cx.tcx.get_diagnostic_name(adt.did()), path.ident.name), + (Some(sym::FsOpenOptions | sym::DirBuilder), sym::mode) + | (Some(sym::FsPermissions), sym::set_mode) + ) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) && param diff --git a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs index 7317c62df7f..2d303e40bd1 100644 --- a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs +++ b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; +use clippy_utils::qualify_min_const_fn::is_stable_const_fn; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; use clippy_utils::visitors::for_each_expr_without_closures; @@ -20,7 +21,7 @@ pub(super) fn check<'tcx>( expr: &'tcx hir::Expr<'_>, assignee: &'tcx hir::Expr<'_>, e: &'tcx hir::Expr<'_>, - _msrv: Msrv, + msrv: Msrv, ) { if let hir::ExprKind::Binary(op, l, r) = &e.kind { let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| { @@ -43,10 +44,28 @@ pub(super) fn check<'tcx>( } } - // Skip if the trait is not stable in const contexts - // FIXME: reintroduce a better check after this is merged back into Clippy + // Skip if the trait or the implementation is not stable in const contexts if is_in_const_context(cx) { - return; + if cx + .tcx + .associated_item_def_ids(trait_id) + .first() + .is_none_or(|binop_id| !is_stable_const_fn(cx, *binop_id, msrv)) + { + return; + } + + let impls = cx.tcx.non_blanket_impls_for_ty(trait_id, rty).collect::<Vec<_>>(); + if impls.is_empty() + || impls.into_iter().any(|impl_id| { + cx.tcx + .associated_item_def_ids(impl_id) + .first() + .is_none_or(|fn_id| !is_stable_const_fn(cx, *fn_id, msrv)) + }) + { + return; + } } span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs index 22ec4fe60fb..604f8f5da0b 100644 --- a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs +++ b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs @@ -47,14 +47,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).is_some_and(|did| { - if cx.tcx.is_diagnostic_item(sym::from_str_method, did) { - true - } else if cx.tcx.is_diagnostic_item(sym::from_fn, did) { - !is_copy(cx, typeck.expr_ty(expr)) - } else { - false - } + if path_def_id(cx, path).is_some_and(|did| match cx.tcx.get_diagnostic_name(did) { + Some(sym::from_str_method) => true, + Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, }) => { (arg, arg.span) diff --git a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs index 047a5a0159c..17fa8017c97 100644 --- a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs +++ b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs @@ -34,10 +34,11 @@ pub(crate) fn check<'tcx>( val_r, ) = lhs.kind - // right hand side matches either f32::EPSILON or f64::EPSILON + // right hand side matches _::EPSILON && let ExprKind::Path(ref epsilon_path) = rhs.kind && let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id) - && ([sym::f32_epsilon, sym::f64_epsilon].into_iter().any(|sym| cx.tcx.is_diagnostic_item(sym, def_id))) + && let Some(sym) = cx.tcx.get_diagnostic_name(def_id) + && matches!(sym, sym::f16_epsilon | sym::f32_epsilon | sym::f64_epsilon | sym::f128_epsilon) // values of the subtractions on the left hand side are of the type float && let t_val_l = cx.typeck_results().expr_ty(val_l) diff --git a/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs index 449d3da7639..43db0085f2e 100644 --- a/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs +++ b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; +use clippy_utils::{is_in_test, is_inside_always_const_context}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; @@ -99,7 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) { if is_panic(cx, macro_call.def_id) { - if cx.tcx.hir_is_inside_const_context(expr.hir_id) + if is_inside_always_const_context(cx.tcx, expr.hir_id) || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; @@ -140,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { && let Res::Def(DefKind::Fn, def_id) = expr_path.res && cx.tcx.is_diagnostic_item(sym::panic_any, def_id) { - if cx.tcx.hir_is_inside_const_context(expr.hir_id) + if is_inside_always_const_context(cx.tcx, expr.hir_id) || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs index e57b8cc2d84..1d58cdd26d8 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_clone.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::fn_has_unsatisfiable_preds; use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, LangItem, def_id}; @@ -96,14 +96,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let (fn_def_id, arg, arg_ty, clone_ret) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); + let fn_name = cx.tcx.get_diagnostic_name(fn_def_id); + let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) - || cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) - || (cx.tcx.is_diagnostic_item(sym::to_string_method, fn_def_id) - && is_type_lang_item(cx, arg_ty, LangItem::String)); + || fn_name == Some(sym::to_owned_method) + || (fn_name == Some(sym::to_string_method) && is_type_lang_item(cx, arg_ty, LangItem::String)); - let from_deref = !from_borrow - && (cx.tcx.is_diagnostic_item(sym::path_to_pathbuf, fn_def_id) - || cx.tcx.is_diagnostic_item(sym::os_str_to_os_string, fn_def_id)); + let from_deref = !from_borrow && matches!(fn_name, Some(sym::path_to_pathbuf | sym::os_str_to_os_string)); if !from_borrow && !from_deref { continue; @@ -148,8 +147,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { is_call_with_ref_arg(cx, mir, &pred_terminator.kind) && res == cloned && cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id) - && (is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) - || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString)) + && let ty::Adt(pred_arg_def, _) = pred_arg_ty.kind() + && let Some(pred_arg_name) = cx.tcx.get_diagnostic_name(pred_arg_def.did()) + && matches!(pred_arg_name, sym::PathBuf | sym::OsString) { (pred_arg, res) } else { diff --git a/src/tools/clippy/clippy_lints/src/semicolon_block.rs b/src/tools/clippy/clippy_lints/src/semicolon_block.rs index db91c57b181..1dea8f17c34 100644 --- a/src/tools/clippy/clippy_lints/src/semicolon_block.rs +++ b/src/tools/clippy/clippy_lints/src/semicolon_block.rs @@ -155,6 +155,11 @@ impl LateLintPass<'_> for SemicolonBlock { kind: ExprKind::Block(block, _), .. }) if !block.span.from_expansion() => { + let attrs = cx.tcx.hir_attrs(stmt.hir_id); + if !attrs.is_empty() && !cx.tcx.features().stmt_expr_attributes() { + return; + } + if let Some(tail) = block.expr { self.semicolon_inside_block(cx, block, tail, stmt.span); } diff --git a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs index e9534bc63a6..8c4a50041e6 100644 --- a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs +++ b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs @@ -126,6 +126,8 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { && !is_from_proc_macro(cx, &first_segment.ident) && !matches!(def_kind, DefKind::Macro(_)) && let Some(last_segment) = path.segments.last() + && let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res + && crate_def_id.is_crate_root() { let (lint, used_mod, replace_with) = match first_segment.ident.name { sym::std => match cx.tcx.crate_name(def_id.krate) { diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs index 490e6c974ae..57d5900b045 100644 --- a/src/tools/clippy/clippy_lints/src/strings.rs +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -457,7 +457,8 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { } fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool { - cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id) + matches!( + cx.tcx.get_diagnostic_name(trim_def_id), + Some(sym::str_trim | sym::str_trim_start | sym::str_trim_end) + ) } diff --git a/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs b/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs index 08f36a2ed5d..543f3c45e14 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -1,8 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use std::borrow::Cow; + +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{HasSession, SpanRangeExt as _}; use rustc_errors::Applicability; -use rustc_hir::{GenericArg, HirId, LetStmt, Node, Path, TyKind}; +use rustc_hir::{Expr, GenericArg, HirId, LetStmt, Node, Path, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; use crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS; @@ -38,6 +42,7 @@ fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool { pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, path: &Path<'tcx>, + arg: &Expr<'tcx>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, expr_hir_id: HirId, @@ -68,14 +73,48 @@ pub(super) fn check<'tcx>( } else if is_function_block(cx, expr_hir_id) { return false; } - span_lint_and_sugg( + let span = last.ident.span.with_hi(path.span.hi()); + span_lint_and_then( cx, MISSING_TRANSMUTE_ANNOTATIONS, - last.ident.span.with_hi(path.span.hi()), + span, "transmute used without annotations", - "consider adding missing annotations", - format!("{}::<{from_ty}, {to_ty}>", last.ident), - Applicability::MaybeIncorrect, + |diag| { + let from_ty_no_name = ty_cannot_be_named(from_ty); + let to_ty_no_name = ty_cannot_be_named(to_ty); + if from_ty_no_name || to_ty_no_name { + let to_name = match (from_ty_no_name, to_ty_no_name) { + (true, false) => maybe_name_by_expr(cx, arg.span, "the origin type"), + (false, true) => "the destination type".into(), + _ => "the source and destination types".into(), + }; + diag.help(format!( + "consider giving {to_name} a name, and adding missing type annotations" + )); + } else { + diag.span_suggestion( + span, + "consider adding missing annotations", + format!("{}::<{from_ty}, {to_ty}>", last.ident), + Applicability::MaybeIncorrect, + ); + } + }, ); true } + +fn ty_cannot_be_named(ty: Ty<'_>) -> bool { + matches!( + ty.kind(), + ty::Alias(ty::AliasTyKind::Opaque | ty::AliasTyKind::Inherent, _) + ) +} + +fn maybe_name_by_expr<'a>(sess: &impl HasSession, span: Span, default: &'a str) -> Cow<'a, str> { + span.with_source_text(sess, |name| { + (name.len() + 9 < default.len()).then_some(format!("`{name}`'s type").into()) + }) + .flatten() + .unwrap_or(default.into()) +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs index 1c7bb4314dd..5fda388259a 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs @@ -520,7 +520,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | transmuting_null::check(cx, e, arg, to_ty) | transmute_null_to_fn::check(cx, e, arg, to_ty) | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv) - | missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id) + | missing_transmute_annotations::check(cx, path, arg, from_ty, to_ty, e.hir_id) | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv) | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg) diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index f27aaa2fa77..2257aa1b73c 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -1,6 +1,6 @@ use super::TRANSMUTE_INT_TO_NON_ZERO; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::sugg; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -16,35 +16,24 @@ pub(super) fn check<'tcx>( to_ty: Ty<'tcx>, arg: &'tcx Expr<'_>, ) -> bool { - let tcx = cx.tcx; - - let (ty::Int(_) | ty::Uint(_), ty::Adt(adt, substs)) = (&from_ty.kind(), to_ty.kind()) else { - return false; - }; - - if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { - return false; + if let ty::Int(_) | ty::Uint(_) = from_ty.kind() + && let ty::Adt(adt, substs) = to_ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_ty = substs.type_at(0) + && from_ty == int_ty + { + let arg = Sugg::hir(cx, arg, ".."); + span_lint_and_sugg( + cx, + TRANSMUTE_INT_TO_NON_ZERO, + e.span, + format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), + "consider using", + format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), + Applicability::Unspecified, + ); + true + } else { + false } - - let int_ty = substs.type_at(0); - if from_ty != int_ty { - return false; - } - - span_lint_and_then( - cx, - TRANSMUTE_INT_TO_NON_ZERO, - e.span, - format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), - |diag| { - let arg = sugg::Sugg::hir(cx, arg, ".."); - diag.span_suggestion( - e.span, - "consider using", - format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), - Applicability::Unspecified, - ); - }, - ); - true } 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 d691f1878b1..c4fd0fbf87a 100644 --- a/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs +++ b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs @@ -11,7 +11,8 @@ use super::RC_BUFFER; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let app = Applicability::Unspecified; - if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + 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( @@ -56,7 +57,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); return true; } - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { + } 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( diff --git a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs index de3456a8ba5..0ba51daf027 100644 --- a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs +++ b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs @@ -13,14 +13,11 @@ use super::{REDUNDANT_ALLOCATION, utils}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>, qpath: &QPath<'tcx>, def_id: DefId) -> bool { let mut applicability = Applicability::MaybeIncorrect; - let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() { - "Box" - } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { - "Rc" - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { - "Arc" - } else { - return false; + let outer_sym = match cx.tcx.get_diagnostic_name(def_id) { + _ if Some(def_id) == cx.tcx.lang_items().owned_box() => "Box", + Some(sym::Rc) => "Rc", + Some(sym::Arc) => "Arc", + _ => return false, }; if let Some(span) = utils::match_borrows_parameter(cx, qpath) { diff --git a/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs b/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs index 48b532968cb..38716519e23 100644 --- a/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs +++ b/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node}; -use clippy_utils::sym; +use clippy_utils::{is_unit_expr, sym}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -16,10 +16,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail", _ => return, }; - let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { + let Some((lhs, rhs, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return; }; - if !cx.typeck_results().expr_ty(left).is_unit() { + if is_unit_expr(lhs) || is_unit_expr(rhs) { + return; + } + if !cx.typeck_results().expr_ty(lhs).is_unit() { return; } span_lint( diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs b/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs index 7d996775a58..28f4884fa31 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -41,7 +41,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { && let ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind() && inner_str.is_str() { - if cx.tcx.is_diagnostic_item(sym::string_new, fun_def_id) { + let fun_name = cx.tcx.get_diagnostic_name(fun_def_id); + if fun_name == Some(sym::string_new) { span_lint_and_sugg( cx, UNNECESSARY_OWNED_EMPTY_STRINGS, @@ -51,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { "\"\"".to_owned(), Applicability::MachineApplicable, ); - } else if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id) + } else if fun_name == Some(sym::from_fn) && let [arg] = args && let ExprKind::Lit(spanned) = &arg.kind && let LitKind::Str(symbol, _) = spanned.node diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs index 54a7efc090a..849c0b438a5 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs @@ -107,12 +107,10 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { // Get the wrapper and inner types, if can't, abort. let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { - ("Option", OptionSome, subst.type_at(0)) - } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { - ("Result", ResultOk, subst.type_at(0)) - } else { - return; + match cx.tcx.get_diagnostic_name(adt_def.did()) { + Some(sym::Option) => ("Option", OptionSome, subst.type_at(0)), + Some(sym::Result) => ("Result", ResultOk, subst.type_at(0)), + _ => return, } } else { return; 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 8b278d98a30..f3410c98973 100644 --- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs +++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs @@ -326,7 +326,11 @@ fn extend_with_struct_pat( if idx_1 == idx { // In the case of `k`, we merely require identical field names // so that we will transform into `ident_k: p1_k | p2_k`. - let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident)); + let pos = fps2.iter().position(|fp2| { + // Avoid `Foo { bar } | Foo { bar }` => `Foo { bar | bar }` + !(fp1.is_shorthand && fp2.is_shorthand) + && eq_id(fp1.ident, fp2.ident) + }); pos_in_2.set(pos); pos.is_some() } else { diff --git a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs index f3cd3f1bb28..af3ad4566c4 100644 --- a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs +++ b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs @@ -89,7 +89,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { { // We don't want to lint inside io::Read or io::Write implementations, as the author has more // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 - if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) { + if let Some(trait_name) = cx.tcx.get_diagnostic_name(trait_id) + && matches!(trait_name, sym::IoRead | sym::IoWrite) + { return; } diff --git a/src/tools/clippy/clippy_lints/src/unused_unit.rs b/src/tools/clippy/clippy_lints/src/unused_unit.rs index 3811f0fe6b5..d503bd3379b 100644 --- a/src/tools/clippy/clippy_lints/src/unused_unit.rs +++ b/src/tools/clippy/clippy_lints/src/unused_unit.rs @@ -6,13 +6,13 @@ use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, Node, PolyTraitRef, Term, - Ty, TyKind, + AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, PolyTraitRef, Term, Ty, + TyKind, }; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::edition::Edition; -use rustc_span::{BytePos, Span, sym}; +use rustc_span::{BytePos, Pos as _, Span, sym}; declare_clippy_lint! { /// ### What it does @@ -49,19 +49,22 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, span: Span, - def_id: LocalDefId, + _def_id: LocalDefId, ) { if let FnRetTy::Return(hir_ty) = decl.output && is_unit_ty(hir_ty) && !hir_ty.span.from_expansion() && get_def(span) == get_def(hir_ty.span) { - // implicit types in closure signatures are forbidden when `for<...>` is present - if let FnKind::Closure = kind - && let Node::Expr(expr) = cx.tcx.hir_node_by_def_id(def_id) - && let ExprKind::Closure(closure) = expr.kind - && !closure.bound_generic_params.is_empty() - { + // The explicit `-> ()` in the closure signature might be necessary for multiple reasons: + // - Implicit types in closure signatures are forbidden when `for<...>` is present + // - If the closure body ends with a function call, and that function's return type is generic, the + // `-> ()` could be required for it to be inferred + // + // There could be more reasons to have it, and, in general, we shouldn't discourage the users from + // writing more type annotations than strictly necessary, because it can help readability and + // maintainability + if let FnKind::Closure = kind { return; } @@ -97,16 +100,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { } fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) { - let segments = &poly.trait_ref.path.segments; - - if segments.len() == 1 - && matches!(segments[0].ident.name, sym::Fn | sym::FnMut | sym::FnOnce) - && let Some(args) = segments[0].args + if let [segment] = &poly.trait_ref.path.segments + && matches!(segment.ident.name, sym::Fn | sym::FnMut | sym::FnOnce) + && let Some(args) = segment.args && args.parenthesized == GenericArgsParentheses::ParenSugar - && let constraints = &args.constraints - && constraints.len() == 1 - && constraints[0].ident.name == sym::Output - && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind + && let [constraint] = &args.constraints + && constraint.ident.name == sym::Output + && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraint.kind && args.span_ext.hi() != poly.span.hi() && !hir_ty.span.from_expansion() && args.span_ext.hi() != hir_ty.span.hi() @@ -160,17 +160,15 @@ fn get_def(span: Span) -> Option<Span> { fn lint_unneeded_unit_return(cx: &LateContext<'_>, ty_span: Span, span: Span) { let (ret_span, appl) = - span.with_hi(ty_span.hi()) - .get_source_text(cx) - .map_or((ty_span, Applicability::MaybeIncorrect), |src| { - position_before_rarrow(&src).map_or((ty_span, Applicability::MaybeIncorrect), |rpos| { - ( - #[expect(clippy::cast_possible_truncation)] - ty_span.with_lo(BytePos(span.lo().0 + rpos as u32)), - Applicability::MachineApplicable, - ) - }) - }); + if let Some(Some(rpos)) = span.with_hi(ty_span.hi()).with_source_text(cx, position_before_rarrow) { + ( + ty_span.with_lo(span.lo() + BytePos::from_usize(rpos)), + Applicability::MachineApplicable, + ) + } else { + (ty_span, Applicability::MaybeIncorrect) + }; + span_lint_and_sugg( cx, UNUSED_UNIT, diff --git a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs index 7bec212a23c..f26647fa348 100644 --- a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs +++ b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::for_each_expr; -use clippy_utils::{method_chain_args, return_ty}; -use core::ops::ControlFlow; -use rustc_hir as hir; -use rustc_hir::ImplItemKind; +use clippy_utils::{return_ty, sym}; +use rustc_hir::{ + Body, BodyOwnerKind, Expr, ExprKind, FnSig, ImplItem, ImplItemKind, Item, ItemKind, OwnerId, PathSegment, QPath, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_middle::ty::Ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Ident, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -57,62 +56,165 @@ declare_clippy_lint! { "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`" } -declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); +impl_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); -impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if let ImplItemKind::Fn(ref _signature, _) = impl_item.kind - // first check if it's a method or function - // checking if its return type is `result` or `option` - && (is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Result) - || is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Option)) - { - lint_impl_body(cx, impl_item.span, impl_item); +#[derive(Clone, Copy, Eq, PartialEq)] +enum OptionOrResult { + Option, + Result, +} + +impl OptionOrResult { + fn with_article(self) -> &'static str { + match self { + Self::Option => "an `Option`", + Self::Result => "a `Result`", } } } -fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) { - if let ImplItemKind::Fn(_, body_id) = impl_item.kind { - let body = cx.tcx.hir_body(body_id); - let typeck = cx.tcx.typeck(impl_item.owner_id.def_id); - let mut result = Vec::new(); - let _: Option<!> = for_each_expr(cx, body.value, |e| { - // check for `expect` - if let Some(arglists) = method_chain_args(e, &[sym::expect]) { - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) - { - result.push(e.span); - } - } - - // check for `unwrap` - if let Some(arglists) = method_chain_args(e, &[sym::unwrap]) { - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) - { - result.push(e.span); - } - } - - ControlFlow::Continue(()) +struct OptionOrResultFn { + kind: OptionOrResult, + return_ty_span: Option<Span>, +} + +#[derive(Default)] +pub struct UnwrapInResult { + fn_stack: Vec<Option<OptionOrResultFn>>, + current_fn: Option<OptionOrResultFn>, +} + +impl UnwrapInResult { + fn enter_item(&mut self, cx: &LateContext<'_>, fn_def_id: OwnerId, sig: &FnSig<'_>) { + self.fn_stack.push(self.current_fn.take()); + self.current_fn = is_option_or_result(cx, return_ty(cx, fn_def_id)).map(|kind| OptionOrResultFn { + kind, + return_ty_span: Some(sig.decl.output.span()), }); + } - // if we've found one, lint - if !result.is_empty() { + fn leave_item(&mut self) { + self.current_fn = self.fn_stack.pop().unwrap(); + } +} + +impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(sig, _) = &impl_item.kind { + self.enter_item(cx, impl_item.owner_id, sig); + } + } + + fn check_impl_item_post(&mut self, _: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'tcx>) { + if let ImplItemKind::Fn(..) = impl_item.kind { + self.leave_item(); + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn { + has_body: true, sig, .. + } = &item.kind + { + self.enter_item(cx, item.owner_id, sig); + } + } + + fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn { has_body: true, .. } = item.kind { + self.leave_item(); + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let Some(OptionOrResultFn { + kind, + ref mut return_ty_span, + }) = self.current_fn + && let Some((oor, fn_name)) = is_unwrap_or_expect_call(cx, expr) + && oor == kind + { span_lint_and_then( cx, UNWRAP_IN_RESULT, - impl_span, - "used unwrap or expect in a function that returns result or option", - move |diag| { - diag.help("unwrap and expect should not be used in a function that returns result or option"); - diag.span_note(result, "potential non-recoverable error(s)"); + expr.span, + format!("`{fn_name}` used in a function that returns {}", kind.with_article()), + |diag| { + // Issue the note and help only once per function + if let Some(span) = return_ty_span.take() { + diag.span_note(span, "in this function signature"); + let complement = if kind == OptionOrResult::Result { + " or calling the `.map_err()` method" + } else { + "" + }; + diag.help(format!("consider using the `?` operator{complement}")); + } }, ); } } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { + // When entering a body which is not a function, mask the potential surrounding + // function to not apply the lint. + self.fn_stack.push(self.current_fn.take()); + } + } + + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { + // Unmask the potential surrounding function. + self.current_fn = self.fn_stack.pop().unwrap(); + } + } +} + +fn is_option_or_result(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<OptionOrResult> { + match ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did())) { + Some(sym::Option) => Some(OptionOrResult::Option), + Some(sym::Result) => Some(OptionOrResult::Result), + _ => None, + } +} + +fn is_unwrap_or_expect_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(OptionOrResult, Symbol)> { + if let ExprKind::Call(func, _) = expr.kind + && let ExprKind::Path(QPath::TypeRelative( + hir_ty, + PathSegment { + ident: + Ident { + name: name @ (sym::unwrap | sym::expect), + .. + }, + .. + }, + )) = func.kind + { + is_option_or_result(cx, cx.typeck_results().node_type(hir_ty.hir_id)).map(|oor| (oor, *name)) + } else if let ExprKind::MethodCall( + PathSegment { + ident: Ident { + name: name @ (sym::unwrap | sym::expect), + .. + }, + .. + }, + recv, + _, + _, + ) = expr.kind + { + is_option_or_result(cx, cx.typeck_results().expr_ty_adjusted(recv)).map(|oor| (oor, *name)) + } else { + None + } } diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs index e5b20c0e0a1..70ae982a445 100644 --- a/src/tools/clippy/clippy_lints/src/useless_conversion.rs +++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs @@ -386,12 +386,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { ExprKind::Call(path, [arg]) => { if let ExprKind::Path(ref qpath) = path.kind - && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && !is_ty_alias(qpath) + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(arg); - if cx.tcx.is_diagnostic_item(sym::try_from_fn, def_id) + if name == sym::try_from_fn && is_type_diagnostic_item(cx, a, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() @@ -406,9 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { None, hint, ); - } - - if cx.tcx.is_diagnostic_item(sym::from_fn, def_id) && same_type_and_consts(a, b) { + } else if name == sym::from_fn && same_type_and_consts(a, b) { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_paren(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); diff --git a/src/tools/clippy/clippy_lints/src/vec.rs b/src/tools/clippy/clippy_lints/src/vec.rs index 7b6a25123e8..52b30ddce12 100644 --- a/src/tools/clippy/clippy_lints/src/vec.rs +++ b/src/tools/clippy/clippy_lints/src/vec.rs @@ -1,4 +1,6 @@ use std::collections::BTreeMap; +use std::collections::btree_map::Entry; +use std::mem; use std::ops::ControlFlow; use clippy_config::Conf; @@ -20,15 +22,36 @@ use rustc_span::{DesugaringKind, Span}; pub struct UselessVec { too_large_for_stack: u64, msrv: Msrv, - span_to_lint_map: BTreeMap<Span, Option<(HirId, SuggestedType, String, Applicability)>>, + /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can + /// emit a warning there or not). + /// + /// The purpose of this is to buffer lints up until `check_crate_post` so that we can cancel a + /// lint while visiting, because a `vec![]` invocation span can appear multiple times when + /// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and + /// another time that does. Consider: + /// ``` + /// macro_rules! m { + /// ($v:expr) => { + /// let a = $v; + /// $v.push(3); + /// } + /// } + /// m!(vec![1, 2]); + /// ``` + /// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing + /// the first `vec![1, 2]` (which is shared with the other expn) to an array which indeed would + /// work, we get a false positive warning on the `$v.push(3)` which really requires `$v` to + /// be a vector. + span_to_state: BTreeMap<Span, VecState>, allow_in_test: bool, } + impl UselessVec { pub fn new(conf: &'static Conf) -> Self { Self { too_large_for_stack: conf.too_large_for_stack, msrv: conf.msrv, - span_to_lint_map: BTreeMap::new(), + span_to_state: BTreeMap::new(), allow_in_test: conf.allow_useless_vec_in_tests, } } @@ -62,17 +85,28 @@ declare_clippy_lint! { impl_lint_pass!(UselessVec => [USELESS_VEC]); -impl<'tcx> LateLintPass<'tcx> for UselessVec { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) else { - return; - }; - if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { - return; - } - // the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!` - let callsite = expr.span.parent_callsite().unwrap_or(expr.span); +/// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed. +enum VecState { + Change { + suggest_ty: SuggestedType, + vec_snippet: String, + expr_hir_id: HirId, + }, + NoChange, +} + +enum VecToArray { + /// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or + /// slice). + Possible, + /// Expression must be a `Vec<_>`. Type cannot change. + Impossible, +} +impl UselessVec { + /// Checks if the surrounding environment requires this expression to actually be of type + /// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors. + fn expr_usage_requires_vec(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) -> VecToArray { match cx.tcx.parent_hir_node(expr.hir_id) { // search for `let foo = vec![_]` expressions where all uses of `foo` // adjust to slices or call a method that exist on slices (e.g. len) @@ -100,110 +134,126 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec { .is_continue(); if only_slice_uses { - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, SuggestedType::Array); + VecToArray::Possible } else { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible } }, // if the local pattern has a specified type, do not lint. Node::LetStmt(LetStmt { ty: Some(_), .. }) if higher::VecArgs::hir(cx, expr).is_some() => { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible }, // search for `for _ in vec![...]` Node::Expr(Expr { span, .. }) if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) => { - let suggest_slice = suggest_type(expr); - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice); + VecToArray::Possible }, // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]` _ => { - let suggest_slice = suggest_type(expr); - if adjusts_to_slice(cx, expr) { - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice); + VecToArray::Possible } else { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible } }, } } +} + +impl<'tcx> LateLintPass<'tcx> for UselessVec { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) + // The `vec![]` or `&vec![]` invocation span. + && let vec_span = expr.span.parent_callsite().unwrap_or(expr.span) + && !vec_span.from_expansion() + { + if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { + return; + } + + match self.expr_usage_requires_vec(cx, expr) { + VecToArray::Possible => { + let suggest_ty = suggest_type(expr); + + // Size and `Copy` checks don't depend on the enclosing usage of the expression + // and don't need to be inserted into the state map. + let vec_snippet = match vec_args { + higher::VecArgs::Repeat(expr, len) => { + if is_copy(cx, cx.typeck_results().expr_ty(expr)) + && let Some(Constant::Int(length)) = ConstEvalCtxt::new(cx).eval(len) + && let Ok(length) = u64::try_from(length) + && size_of(cx, expr) + .checked_mul(length) + .is_some_and(|size| size <= self.too_large_for_stack) + { + suggest_ty.snippet( + cx, + Some(expr.span.source_callsite()), + Some(len.span.source_callsite()), + ) + } else { + return; + } + }, + higher::VecArgs::Vec(args) => { + if let Ok(length) = u64::try_from(args.len()) + && size_of(cx, expr) + .checked_mul(length) + .is_some_and(|size| size <= self.too_large_for_stack) + { + suggest_ty.snippet( + cx, + args.first().zip(args.last()).map(|(first, last)| { + first.span.source_callsite().to(last.span.source_callsite()) + }), + None, + ) + } else { + return; + } + }, + }; + + if let Entry::Vacant(entry) = self.span_to_state.entry(vec_span) { + entry.insert(VecState::Change { + suggest_ty, + vec_snippet, + expr_hir_id: expr.hir_id, + }); + } + }, + VecToArray::Impossible => { + self.span_to_state.insert(vec_span, VecState::NoChange); + }, + } + } + } fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - for (span, lint_opt) in &self.span_to_lint_map { - if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt { - let help_msg = format!("you can use {} directly", suggest_slice.desc()); - span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| { - // If the `vec!` macro contains comment, better not make the suggestion machine - // applicable as it would remove them. - let applicability = if *applicability != Applicability::Unspecified - && let source_map = cx.tcx.sess.source_map() - && span_contains_comment(source_map, *span) - { + for (span, state) in mem::take(&mut self.span_to_state) { + if let VecState::Change { + suggest_ty, + vec_snippet, + expr_hir_id, + } = state + { + span_lint_hir_and_then(cx, USELESS_VEC, expr_hir_id, span, "useless use of `vec!`", |diag| { + let help_msg = format!("you can use {} directly", suggest_ty.desc()); + // If the `vec!` macro contains comment, better not make the suggestion machine applicable as it + // would remove them. + let applicability = if span_contains_comment(cx.tcx.sess.source_map(), span) { Applicability::Unspecified } else { - *applicability + Applicability::MachineApplicable }; - diag.span_suggestion(*span, help_msg, snippet, applicability); + diag.span_suggestion(span, help_msg, vec_snippet, applicability); }); } } } } -impl UselessVec { - fn check_vec_macro<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - vec_args: &higher::VecArgs<'tcx>, - span: Span, - hir_id: HirId, - suggest_slice: SuggestedType, - ) { - if span.from_expansion() { - return; - } - - let snippet = match *vec_args { - higher::VecArgs::Repeat(elem, len) => { - if let Some(Constant::Int(len_constant)) = ConstEvalCtxt::new(cx).eval(len) { - // vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also - if !is_copy(cx, cx.typeck_results().expr_ty(elem)) { - return; - } - - #[expect(clippy::cast_possible_truncation)] - if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack { - return; - } - - suggest_slice.snippet(cx, Some(elem.span), Some(len.span)) - } else { - return; - } - }, - higher::VecArgs::Vec(args) => { - let args_span = if let Some(last) = args.iter().last() { - if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack { - return; - } - Some(args[0].span.source_callsite().to(last.span.source_callsite())) - } else { - None - }; - suggest_slice.snippet(cx, args_span, None) - }, - }; - - self.span_to_lint_map.entry(span).or_insert(Some(( - hir_id, - suggest_slice, - snippet, - Applicability::MachineApplicable, - ))); - } -} - #[derive(Copy, Clone)] pub(crate) enum SuggestedType { /// Suggest using a slice `&[..]` / `&mut [..]` @@ -221,11 +271,17 @@ impl SuggestedType { } fn snippet(self, cx: &LateContext<'_>, args_span: Option<Span>, len_span: Option<Span>) -> String { + // Invariant of the lint as implemented: all spans are from the root context (and as a result, + // always trivially crate-local). + assert!(args_span.is_none_or(|s| !s.from_expansion())); + assert!(len_span.is_none_or(|s| !s.from_expansion())); + let maybe_args = args_span - .and_then(|sp| sp.get_source_text(cx)) + .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) .map_or(String::new(), |x| x.to_owned()); let maybe_len = len_span - .and_then(|sp| sp.get_source_text(cx).map(|s| format!("; {s}"))) + .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) + .map(|st| format!("; {st}")) .unwrap_or_default(); match self { diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index a15e4e42e71..c55c5ec2f51 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -537,7 +537,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - if let Some((_, index)) = positional_arg_piece_span(piece) { + if let Some((_, index)) = format_arg_piece_span(piece) { replaced_position.push(index); } @@ -569,16 +569,11 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { } } -/// Extract Span and its index from the given `piece`, if it's positional argument. -fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { +/// 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), - kind: FormatArgPositionKind::Number, - .. - }, + argument: FormatArgPosition { index: Ok(index), .. }, span: Some(span), .. }) => Some((*span, *index)), diff --git a/src/tools/clippy/clippy_lints/src/zombie_processes.rs b/src/tools/clippy/clippy_lints/src/zombie_processes.rs index 6ab94a52210..a934d2094e0 100644 --- a/src/tools/clippy/clippy_lints/src/zombie_processes.rs +++ b/src/tools/clippy/clippy_lints/src/zombie_processes.rs @@ -1,5 +1,6 @@ use ControlFlow::{Break, Continue}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{fn_def_id, get_enclosing_block, path_to_local_id}; use rustc_ast::Mutability; use rustc_ast::visit::visit_opt; @@ -58,8 +59,8 @@ declare_lint_pass!(ZombieProcesses => [ZOMBIE_PROCESSES]); impl<'tcx> LateLintPass<'tcx> for ZombieProcesses { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let ExprKind::Call(..) | ExprKind::MethodCall(..) = expr.kind - && let Some(child_adt) = cx.typeck_results().expr_ty(expr).ty_adt_def() - && cx.tcx.is_diagnostic_item(sym::Child, child_adt.did()) + && let child_ty = cx.typeck_results().expr_ty(expr) + && is_type_diagnostic_item(cx, child_ty, sym::Child) { match cx.tcx.parent_hir_node(expr.hir_id) { Node::LetStmt(local) @@ -177,8 +178,8 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {}, Node::Expr(expr) if let Some(fn_did) = fn_def_id(self.cx, expr) - && (self.cx.tcx.is_diagnostic_item(sym::child_id, fn_did) - || self.cx.tcx.is_diagnostic_item(sym::child_kill, fn_did)) => {}, + && let Some(fn_name) = self.cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::child_id | sym::child_kill) => {}, // Conservatively assume that all other kinds of nodes call `.wait()` somehow. _ => return Break(MaybeWait(ex.span)), @@ -351,9 +352,14 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus /// Checks if the given expression exits the process. fn is_exit_expression(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - fn_def_id(cx, expr).is_some_and(|fn_did| { - cx.tcx.is_diagnostic_item(sym::process_exit, fn_did) || cx.tcx.is_diagnostic_item(sym::process_abort, fn_did) - }) + if let Some(fn_did) = fn_def_id(cx, expr) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::process_exit | sym::process_abort) + { + true + } else { + false + } } #[derive(Debug)] diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md index 2dfe28953d0..e01f563c49e 100644 --- a/src/tools/clippy/clippy_utils/README.md +++ b/src/tools/clippy/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: <!-- begin autogenerated nightly --> ``` -nightly-2025-08-22 +nightly-2025-09-04 ``` <!-- end autogenerated nightly --> diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 40c00568a3b..ad69e6eb184 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -21,7 +21,7 @@ pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool { } /// Checks if each element in the first slice is contained within the latter as per `eq_fn`. -pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { +pub fn unordered_over<X, Y>(left: &[X], right: &[Y], mut eq_fn: impl FnMut(&X, &Y) -> bool) -> bool { left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r))) } diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs index 4e0b00df950..bda28a663fb 100644 --- a/src/tools/clippy/clippy_utils/src/higher.rs +++ b/src/tools/clippy/clippy_utils/src/higher.rs @@ -287,23 +287,22 @@ impl<'a> VecArgs<'a> { && let ExprKind::Path(ref qpath) = fun.kind && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id) { - return if cx.tcx.is_diagnostic_item(sym::vec_from_elem, fun_def_id) && args.len() == 2 { - // `vec![elem; size]` case - Some(VecArgs::Repeat(&args[0], &args[1])) - } else if cx.tcx.is_diagnostic_item(sym::slice_into_vec, fun_def_id) && args.len() == 1 { - // `vec![a, b, c]` case - if let ExprKind::Call(_, [arg]) = &args[0].kind - && let ExprKind::Array(args) = arg.kind + return match (name, args) { + (sym::vec_from_elem, [elem, size]) => { + // `vec![elem; size]` case + Some(VecArgs::Repeat(elem, size)) + }, + (sym::slice_into_vec, [slice]) + if let ExprKind::Call(_, [arg]) = slice.kind + && let ExprKind::Array(args) = arg.kind => { + // `vec![a, b, c]` case Some(VecArgs::Vec(args)) - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::vec_new, fun_def_id) && args.is_empty() { - Some(VecArgs::Vec(&[])) - } else { - None + }, + (sym::vec_new, []) => Some(VecArgs::Vec(&[])), + _ => None, }; } diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 8160443f413..b79e15cd717 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -757,7 +757,7 @@ pub fn both_some_and<X, Y>(l: Option<X>, r: Option<Y>, mut pred: impl FnMut(X, Y } /// Checks if two slices are equal as per `eq_fn`. -pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { +pub fn over<X, Y>(left: &[X], right: &[Y], mut eq_fn: impl FnMut(&X, &Y) -> bool) -> bool { left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y)) } diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 011c9b2f931..14b64eb4d54 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -102,9 +102,9 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr}; use rustc_hir::{ self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring, - CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, - ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, - Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, + CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, + HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, + OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; @@ -126,6 +126,7 @@ use rustc_span::{InnerSpan, Span}; use source::{SpanRangeExt, walk_span_to_context}; use visitors::{Visitable, for_each_unconsumed_temporary}; +use crate::ast_utils::unordered_over; use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; use crate::higher::Range; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; @@ -308,6 +309,7 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> cx.tcx.lang_items().get(item) == Some(did) } +/// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( expr.kind, @@ -640,9 +642,8 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id) && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { - return std_types_symbols.iter().any(|&symbol| { - cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string() - }); + return Some(adt.did()) == cx.tcx.lang_items().string() + || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name)); } false } @@ -1992,7 +1993,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup)) if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() => { - zip(pats, tup).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir)) }, (PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => { zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) @@ -2004,7 +2005,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< if let ExprKind::Path(ident) = &ident.kind && qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id) // check fields - && zip(field_pats, fields).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr,by_hir)) + && over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir)) { true } else { @@ -2017,10 +2018,8 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< // check ident qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id) // check fields - && field_pats.iter().all(|field_pat| { - fields.iter().any(|field| { - field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir) - }) + && unordered_over(field_pats, fields, |field_pat, field| { + field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir) }) }, _ => false, @@ -2405,6 +2404,24 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir> expr } +/// Returns a `Vec` of `Expr`s containing `AddrOf` operators (`&`) or deref operators (`*`) of a +/// given expression. +pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> { + let mut operators = Vec::new(); + peel_hir_expr_while(expr, |expr| match expr.kind { + ExprKind::AddrOf(_, _, e) => { + operators.push(expr); + Some(e) + }, + ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => { + operators.push(expr); + Some(e) + }, + _ => None, + }); + operators +} + pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind && let Res::Def(_, def_id) = path.res @@ -3633,3 +3650,17 @@ pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) ) }) } + +/// Checks if the expression is an async block (i.e., `async { ... }`). +pub fn is_expr_async_block(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Closure(Closure { + kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared( + CoroutineDesugaring::Async, + CoroutineSource::Block + )), + .. + }) + ) +} diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs index fafc1d07e51..8e302f9d2ad 100644 --- a/src/tools/clippy/clippy_utils/src/ty/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs @@ -387,10 +387,7 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { /// Checks if the type is a reference equals to a diagnostic item pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { match ty.kind() { - ty::Ref(_, ref_ty, _) => match ref_ty.kind() { - ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()), - _ => false, - }, + ty::Ref(_, ref_ty, _) => is_type_diagnostic_item(cx, *ref_ty, diag_item), _ => false, } } @@ -1378,9 +1375,7 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<' /// Check if `ty` is slice-like, i.e., `&[T]`, `[T; N]`, or `Vec<T>`. pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - ty.is_slice() - || ty.is_array() - || matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did())) + ty.is_slice() || ty.is_array() || is_type_diagnostic_item(cx, ty, sym::Vec) } pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> { diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs index 76d43feee12..6eccbcdb122 100644 --- a/src/tools/clippy/clippy_utils/src/usage.rs +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -144,11 +144,9 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> { /// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`. pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - root_macro_call_first_node(cx, expr).is_some_and(|macro_call| { - [sym::todo_macro, sym::unimplemented_macro] - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id)) - }) + root_macro_call_first_node(cx, expr) + .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id)) + .is_some_and(|macro_name| matches!(macro_name, sym::todo_macro | sym::unimplemented_macro)) } /// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression, diff --git a/src/tools/clippy/rust-toolchain.toml b/src/tools/clippy/rust-toolchain.toml index 5497e77e8ad..ec2f24a0a6d 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-08-22" +channel = "nightly-2025-09-04" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs index c4076cbaa77..6bddcbfd94c 100644 --- a/src/tools/clippy/src/driver.rs +++ b/src/tools/clippy/src/driver.rs @@ -330,7 +330,8 @@ pub fn main() { // Do not run Clippy for Cargo's info queries so that invalid CLIPPY_ARGS are not cached // https://github.com/rust-lang/cargo/issues/14385 - let info_query = has_arg(&orig_args, "-vV") || has_arg(&orig_args, "--print"); + let info_query = has_arg(&orig_args, "-vV") + || arg_value(&orig_args, "--print", |val| val != "crate-root-lint-levels").is_some(); let clippy_enabled = !cap_lints_allow && relevant_package && !info_query; if clippy_enabled { diff --git a/src/tools/clippy/tests/config-consistency.rs b/src/tools/clippy/tests/config-consistency.rs new file mode 100644 index 00000000000..9e7ca26c7d4 --- /dev/null +++ b/src/tools/clippy/tests/config-consistency.rs @@ -0,0 +1,30 @@ +#![feature(rustc_private)] + +// This test checks that all lints defined in `clippy_config::conf` in `#[lints]` +// attributes exist as Clippy lints. +// +// This test is a no-op if run as part of the compiler test suite +// and will always succeed. + +use std::collections::HashSet; + +#[test] +fn config_consistency() { + if option_env!("RUSTC_TEST_SUITE").is_some() { + return; + } + + let lint_names: HashSet<String> = clippy_lints::declared_lints::LINTS + .iter() + .map(|lint_info| lint_info.lint.name.strip_prefix("clippy::").unwrap().to_lowercase()) + .collect(); + for conf in clippy_config::get_configuration_metadata() { + for lint in conf.lints { + assert!( + lint_names.contains(*lint), + "Configuration option {} references lint `{lint}` which does not exist", + conf.name + ); + } + } +} diff --git a/src/tools/clippy/tests/no-profile-in-cargo-toml.rs b/src/tools/clippy/tests/no-profile-in-cargo-toml.rs new file mode 100644 index 00000000000..2ad9bfb75de --- /dev/null +++ b/src/tools/clippy/tests/no-profile-in-cargo-toml.rs @@ -0,0 +1,34 @@ +// Check that we do not have `profile.*` sections in our `Cargo.toml` files, +// as this causes warnings when run from the compiler repository which includes +// Clippy in a workspace. +// +// Those sections can be put into `.cargo/config.toml` which will be read +// when commands are issued from the top-level Clippy directory, outside of +// a workspace. + +use std::fs::File; +use std::io::{self, BufRead as _}; +use walkdir::WalkDir; + +#[test] +fn no_profile_in_cargo_toml() { + // This check could parse `Cargo.toml` using a TOML deserializer, but in practice + // profile sections would be added at the beginning of a line as `[profile.*]`, so + // keep it fast and simple. + for entry in WalkDir::new(".") + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_name().to_str() == Some("Cargo.toml")) + { + for line in io::BufReader::new(File::open(entry.path()).unwrap()) + .lines() + .map(Result::unwrap) + { + if line.starts_with("[profile.") { + eprintln!("Profile section `{line}` found in file `{}`.", entry.path().display()); + eprintln!("Use `.cargo/config.toml` for profiles specific to the standalone Clippy repository."); + panic!("Profile section found in `Cargo.toml`"); + } + } + } +} diff --git a/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr b/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr index f03a745963e..e9d53c64dd9 100644 --- a/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr +++ b/src/tools/clippy/tests/ui-internal/disallow_span_lint.stderr @@ -4,7 +4,7 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` LL | cx.span_lint(lint, span, |lint| { | ^^^^^^^^^ | - = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead + = note: this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead note: the lint level is defined here --> tests/ui-internal/disallow_span_lint.rs:2:9 | @@ -17,7 +17,7 @@ error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_ LL | tcx.node_span_lint(lint, hir_id, span, |lint| { | ^^^^^^^^^^^^^^ | - = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead + = note: this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead error: aborting due to 2 previous errors diff --git a/src/tools/clippy/tests/ui-toml/excessive_precision/clippy.toml b/src/tools/clippy/tests/ui-toml/excessive_precision/clippy.toml new file mode 100644 index 00000000000..c7fc230dcb3 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/excessive_precision/clippy.toml @@ -0,0 +1 @@ +const-literal-digits-threshold = 20 diff --git a/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.fixed b/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.fixed new file mode 100644 index 00000000000..577bbff2957 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.fixed @@ -0,0 +1,38 @@ +#![warn(clippy::excessive_precision)] +#![allow( + dead_code, + overflowing_literals, + unused_variables, + clippy::print_literal, + clippy::useless_vec +)] + +fn main() { + // Overly specified constants + let _: f32 = 1.012_345_7; + //~^ excessive_precision + let _: f64 = 1.012_345_678_901_234_6; + //~^ excessive_precision + const _: f32 = 1.012345678901234567890; + const _: f64 = 1.012345678901234567890; + + static STATIC1: f32 = 1.012345678901234567890; + static STATIC2: f64 = 1.012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.012345678901234567890; + static mut STATIC_MUT2: f64 = 1.012345678901234567890; +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} diff --git a/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.rs b/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.rs new file mode 100644 index 00000000000..121448ed540 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.rs @@ -0,0 +1,38 @@ +#![warn(clippy::excessive_precision)] +#![allow( + dead_code, + overflowing_literals, + unused_variables, + clippy::print_literal, + clippy::useless_vec +)] + +fn main() { + // Overly specified constants + let _: f32 = 1.012345678901234567890; + //~^ excessive_precision + let _: f64 = 1.012345678901234567890; + //~^ excessive_precision + const _: f32 = 1.012345678901234567890; + const _: f64 = 1.012345678901234567890; + + static STATIC1: f32 = 1.012345678901234567890; + static STATIC2: f64 = 1.012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.012345678901234567890; + static mut STATIC_MUT2: f64 = 1.012345678901234567890; +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} diff --git a/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.stderr b/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.stderr new file mode 100644 index 00000000000..65d33eddef1 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/excessive_precision/excessive_precision.stderr @@ -0,0 +1,38 @@ +error: float has excessive precision + --> tests/ui-toml/excessive_precision/excessive_precision.rs:12:18 + | +LL | let _: f32 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui-toml/excessive_precision/excessive_precision.rs:12:5 + | +LL | let _: f32 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::excessive-precision` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::excessive_precision)]` +help: consider changing the type or truncating it to + | +LL - let _: f32 = 1.012345678901234567890; +LL + let _: f32 = 1.012_345_7; + | + +error: float has excessive precision + --> tests/ui-toml/excessive_precision/excessive_precision.rs:14:18 + | +LL | let _: f64 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui-toml/excessive_precision/excessive_precision.rs:14:5 + | +LL | let _: f64 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let _: f64 = 1.012345678901234567890; +LL + let _: f64 = 1.012_345_678_901_234_6; + | + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6ee77ebd8ec..20aeb4bb849 100644 --- a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -35,6 +35,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect check-inconsistent-struct-field-initializers check-private-items cognitive-complexity-threshold + const-literal-digits-threshold disallowed-macros disallowed-methods disallowed-names @@ -129,6 +130,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect check-inconsistent-struct-field-initializers check-private-items cognitive-complexity-threshold + const-literal-digits-threshold disallowed-macros disallowed-methods disallowed-names @@ -223,6 +225,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni check-inconsistent-struct-field-initializers check-private-items cognitive-complexity-threshold + const-literal-digits-threshold disallowed-macros disallowed-methods disallowed-names diff --git a/src/tools/clippy/tests/ui/assertions_on_result_states.fixed b/src/tools/clippy/tests/ui/assertions_on_result_states.fixed index 09c1a8d0ed1..b2b7318c113 100644 --- a/src/tools/clippy/tests/ui/assertions_on_result_states.fixed +++ b/src/tools/clippy/tests/ui/assertions_on_result_states.fixed @@ -82,9 +82,18 @@ fn main() { assert!(r.is_err()); } -#[allow(dead_code)] fn issue9450() { let res: Result<i32, i32> = Ok(1); res.unwrap_err(); //~^ assertions_on_result_states } + +fn issue9916(res: Result<u32, u32>) { + let a = 0; + match a { + 0 => {}, + 1 => { res.unwrap(); }, + //~^ assertions_on_result_states + _ => todo!(), + } +} diff --git a/src/tools/clippy/tests/ui/assertions_on_result_states.rs b/src/tools/clippy/tests/ui/assertions_on_result_states.rs index c63c2502b53..33f1485326b 100644 --- a/src/tools/clippy/tests/ui/assertions_on_result_states.rs +++ b/src/tools/clippy/tests/ui/assertions_on_result_states.rs @@ -82,9 +82,18 @@ fn main() { assert!(r.is_err()); } -#[allow(dead_code)] fn issue9450() { let res: Result<i32, i32> = Ok(1); assert!(res.is_err()) //~^ assertions_on_result_states } + +fn issue9916(res: Result<u32, u32>) { + let a = 0; + match a { + 0 => {}, + 1 => assert!(res.is_ok()), + //~^ assertions_on_result_states + _ => todo!(), + } +} diff --git a/src/tools/clippy/tests/ui/assertions_on_result_states.stderr b/src/tools/clippy/tests/ui/assertions_on_result_states.stderr index 3bf811588c6..826f049555c 100644 --- a/src/tools/clippy/tests/ui/assertions_on_result_states.stderr +++ b/src/tools/clippy/tests/ui/assertions_on_result_states.stderr @@ -38,10 +38,16 @@ LL | assert!(r.is_err()); | ^^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap_err()` error: called `assert!` with `Result::is_err` - --> tests/ui/assertions_on_result_states.rs:88:5 + --> tests/ui/assertions_on_result_states.rs:87:5 | LL | assert!(res.is_err()) | ^^^^^^^^^^^^^^^^^^^^^ help: replace with: `res.unwrap_err();` -error: aborting due to 7 previous errors +error: called `assert!` with `Result::is_ok` + --> tests/ui/assertions_on_result_states.rs:95:14 + | +LL | 1 => assert!(res.is_ok()), + | ^^^^^^^^^^^^^^^^^^^^ help: replace with: `{ res.unwrap(); }` + +error: aborting due to 8 previous errors diff --git a/src/tools/clippy/tests/ui/assign_ops.fixed b/src/tools/clippy/tests/ui/assign_ops.fixed index 3754b9dfe74..2046d089d6a 100644 --- a/src/tools/clippy/tests/ui/assign_ops.fixed +++ b/src/tools/clippy/tests/ui/assign_ops.fixed @@ -1,8 +1,9 @@ #![allow(clippy::useless_vec)] #![warn(clippy::assign_op_pattern)] -#![feature(const_trait_impl, const_ops)] +#![feature(const_ops)] +#![feature(const_trait_impl)] -use core::num::Wrapping; +use std::num::Wrapping; use std::ops::{Mul, MulAssign}; fn main() { @@ -82,6 +83,18 @@ mod issue14871 { pub trait Number: Copy + Add<Self, Output = Self> + AddAssign { const ZERO: Self; const ONE: Self; + + fn non_constant(value: usize) -> Self { + let mut res = Self::ZERO; + let mut count = 0; + while count < value { + res += Self::ONE; + //~^ assign_op_pattern + count += 1; + //~^ assign_op_pattern + } + res + } } #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) @@ -91,7 +104,7 @@ mod issue14871 { impl<T> const NumberConstants for T where - T: Number + [const] core::ops::Add, + T: Number + [const] std::ops::Add, { fn constant(value: usize) -> Self { let mut res = Self::ZERO; @@ -99,8 +112,28 @@ mod issue14871 { while count < value { res = res + Self::ONE; count += 1; + //~^ assign_op_pattern } res } } + + pub struct S; + + impl const std::ops::Add for S { + type Output = S; + fn add(self, _rhs: S) -> S { + S + } + } + + impl const std::ops::AddAssign for S { + fn add_assign(&mut self, rhs: S) {} + } + + const fn do_add() { + let mut s = S; + s += S; + //~^ assign_op_pattern + } } diff --git a/src/tools/clippy/tests/ui/assign_ops.rs b/src/tools/clippy/tests/ui/assign_ops.rs index 0b878d4f490..f83a40f5547 100644 --- a/src/tools/clippy/tests/ui/assign_ops.rs +++ b/src/tools/clippy/tests/ui/assign_ops.rs @@ -1,8 +1,9 @@ #![allow(clippy::useless_vec)] #![warn(clippy::assign_op_pattern)] -#![feature(const_trait_impl, const_ops)] +#![feature(const_ops)] +#![feature(const_trait_impl)] -use core::num::Wrapping; +use std::num::Wrapping; use std::ops::{Mul, MulAssign}; fn main() { @@ -82,6 +83,18 @@ mod issue14871 { pub trait Number: Copy + Add<Self, Output = Self> + AddAssign { const ZERO: Self; const ONE: Self; + + fn non_constant(value: usize) -> Self { + let mut res = Self::ZERO; + let mut count = 0; + while count < value { + res = res + Self::ONE; + //~^ assign_op_pattern + count = count + 1; + //~^ assign_op_pattern + } + res + } } #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) @@ -91,16 +104,36 @@ mod issue14871 { impl<T> const NumberConstants for T where - T: Number + [const] core::ops::Add, + T: Number + [const] std::ops::Add, { fn constant(value: usize) -> Self { let mut res = Self::ZERO; let mut count = 0; while count < value { res = res + Self::ONE; - count += 1; + count = count + 1; + //~^ assign_op_pattern } res } } + + pub struct S; + + impl const std::ops::Add for S { + type Output = S; + fn add(self, _rhs: S) -> S { + S + } + } + + impl const std::ops::AddAssign for S { + fn add_assign(&mut self, rhs: S) {} + } + + const fn do_add() { + let mut s = S; + s = s + S; + //~^ assign_op_pattern + } } diff --git a/src/tools/clippy/tests/ui/assign_ops.stderr b/src/tools/clippy/tests/ui/assign_ops.stderr index c5e698b3ee1..a4fca04893c 100644 --- a/src/tools/clippy/tests/ui/assign_ops.stderr +++ b/src/tools/clippy/tests/ui/assign_ops.stderr @@ -1,5 +1,5 @@ error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:10:5 + --> tests/ui/assign_ops.rs:11:5 | LL | a = a + 1; | ^^^^^^^^^ help: replace it with: `a += 1` @@ -8,70 +8,94 @@ LL | a = a + 1; = help: to override `-D warnings` add `#[allow(clippy::assign_op_pattern)]` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:12:5 + --> tests/ui/assign_ops.rs:13:5 | LL | a = 1 + a; | ^^^^^^^^^ help: replace it with: `a += 1` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:14:5 + --> tests/ui/assign_ops.rs:15:5 | LL | a = a - 1; | ^^^^^^^^^ help: replace it with: `a -= 1` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:16:5 + --> tests/ui/assign_ops.rs:17:5 | LL | a = a * 99; | ^^^^^^^^^^ help: replace it with: `a *= 99` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:18:5 + --> tests/ui/assign_ops.rs:19:5 | LL | a = 42 * a; | ^^^^^^^^^^ help: replace it with: `a *= 42` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:20:5 + --> tests/ui/assign_ops.rs:21:5 | LL | a = a / 2; | ^^^^^^^^^ help: replace it with: `a /= 2` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:22:5 + --> tests/ui/assign_ops.rs:23:5 | LL | a = a % 5; | ^^^^^^^^^ help: replace it with: `a %= 5` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:24:5 + --> tests/ui/assign_ops.rs:25:5 | LL | a = a & 1; | ^^^^^^^^^ help: replace it with: `a &= 1` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:31:5 + --> tests/ui/assign_ops.rs:32:5 | LL | s = s + "bla"; | ^^^^^^^^^^^^^ help: replace it with: `s += "bla"` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:36:5 + --> tests/ui/assign_ops.rs:37:5 | LL | a = a + Wrapping(1u32); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a += Wrapping(1u32)` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:39:5 + --> tests/ui/assign_ops.rs:40:5 | LL | v[0] = v[0] + v[1]; | ^^^^^^^^^^^^^^^^^^ help: replace it with: `v[0] += v[1]` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:52:5 + --> tests/ui/assign_ops.rs:53:5 | LL | buf = buf + cows.clone(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `buf += cows.clone()` -error: aborting due to 12 previous errors +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:91:17 + | +LL | res = res + Self::ONE; + | ^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `res += Self::ONE` + +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:93:17 + | +LL | count = count + 1; + | ^^^^^^^^^^^^^^^^^ help: replace it with: `count += 1` + +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:114:17 + | +LL | count = count + 1; + | ^^^^^^^^^^^^^^^^^ help: replace it with: `count += 1` + +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:136:9 + | +LL | s = s + S; + | ^^^^^^^^^ help: replace it with: `s += S` + +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/async_yields_async.fixed b/src/tools/clippy/tests/ui/async_yields_async.fixed index 93c573d3086..bd1b31f74ee 100644 --- a/src/tools/clippy/tests/ui/async_yields_async.fixed +++ b/src/tools/clippy/tests/ui/async_yields_async.fixed @@ -80,3 +80,42 @@ fn check_expect_suppression() { } }; } + +#[allow(clippy::let_underscore_future)] +fn issue15552() { + async fn bar(i: i32) {} + + macro_rules! call_bar { + () => { + async { bar(5).await } + }; + ($e:expr) => { + bar($e) + }; + } + let x = async { call_bar!(5).await }; + //~^ async_yields_async + let y = async { call_bar!().await }; + //~^ async_yields_async + //~| async_yields_async + + use std::future::{Future, Ready}; + use std::ops::Add; + use std::pin::Pin; + use std::task::{Context, Poll}; + struct CustomFutureType; + impl Add for CustomFutureType { + type Output = Self; + fn add(self, other: Self) -> Self { + self + } + } + impl Future for CustomFutureType { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> { + Poll::Ready(()) + } + } + let _ = async { (CustomFutureType + CustomFutureType).await }; + //~^ async_yields_async +} diff --git a/src/tools/clippy/tests/ui/async_yields_async.rs b/src/tools/clippy/tests/ui/async_yields_async.rs index 166d522e1c0..605d2734157 100644 --- a/src/tools/clippy/tests/ui/async_yields_async.rs +++ b/src/tools/clippy/tests/ui/async_yields_async.rs @@ -80,3 +80,42 @@ fn check_expect_suppression() { } }; } + +#[allow(clippy::let_underscore_future)] +fn issue15552() { + async fn bar(i: i32) {} + + macro_rules! call_bar { + () => { + async { bar(5) } + }; + ($e:expr) => { + bar($e) + }; + } + let x = async { call_bar!(5) }; + //~^ async_yields_async + let y = async { call_bar!() }; + //~^ async_yields_async + //~| async_yields_async + + use std::future::{Future, Ready}; + use std::ops::Add; + use std::pin::Pin; + use std::task::{Context, Poll}; + struct CustomFutureType; + impl Add for CustomFutureType { + type Output = Self; + fn add(self, other: Self) -> Self { + self + } + } + impl Future for CustomFutureType { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> { + Poll::Ready(()) + } + } + let _ = async { CustomFutureType + CustomFutureType }; + //~^ async_yields_async +} diff --git a/src/tools/clippy/tests/ui/async_yields_async.stderr b/src/tools/clippy/tests/ui/async_yields_async.stderr index 17a35076fa3..3041cf65789 100644 --- a/src/tools/clippy/tests/ui/async_yields_async.stderr +++ b/src/tools/clippy/tests/ui/async_yields_async.stderr @@ -89,5 +89,50 @@ LL | | CustomFutureType LL | | }; | |_____- outer async construct -error: aborting due to 6 previous errors +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:96:21 + | +LL | let x = async { call_bar!(5) }; + | --^^^^^^^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `call_bar!(5).await` + | outer async construct + +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:98:21 + | +LL | let y = async { call_bar!() }; + | --^^^^^^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `call_bar!().await` + | outer async construct + +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:90:21 + | +LL | async { bar(5) } + | --^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `bar(5).await` + | outer async construct +... +LL | let y = async { call_bar!() }; + | ----------- in this macro invocation + | + = note: this error originates in the macro `call_bar` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:119:21 + | +LL | let _ = async { CustomFutureType + CustomFutureType }; + | --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `(CustomFutureType + CustomFutureType).await` + | outer async construct + +error: aborting due to 10 previous errors diff --git a/src/tools/clippy/tests/ui/bool_comparison.fixed b/src/tools/clippy/tests/ui/bool_comparison.fixed index 166abbe549c..b0b60104c0b 100644 --- a/src/tools/clippy/tests/ui/bool_comparison.fixed +++ b/src/tools/clippy/tests/ui/bool_comparison.fixed @@ -1,97 +1,41 @@ #![allow(non_local_definitions, clippy::needless_if)] #![warn(clippy::bool_comparison)] -#![allow(clippy::non_canonical_partial_ord_impl, clippy::nonminimal_bool)] +#![allow(clippy::non_canonical_partial_ord_impl)] fn main() { let x = true; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let y = true; - if !x & y { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x & !y { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if !x & y { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x & !y { "yes" } else { "no" }; + //~^ bool_comparison } -#[allow(dead_code)] fn issue3703() { struct Foo; impl PartialEq<bool> for Foo { @@ -127,26 +71,6 @@ fn issue3703() { if false < Foo {} } -#[allow(dead_code)] -fn issue4983() { - let a = true; - let b = false; - - if a != b {}; - //~^ bool_comparison - if a != b {}; - //~^ bool_comparison - if a == b {}; - if !a == !b {}; - - if b != a {}; - //~^ bool_comparison - if b != a {}; - //~^ bool_comparison - if b == a {}; - if !b == !a {}; -} - macro_rules! m { ($func:ident) => { $func() @@ -157,7 +81,6 @@ fn func() -> bool { true } -#[allow(dead_code)] fn issue3973() { // ok, don't lint on `cfg` invocation if false == cfg!(feature = "debugging") {} @@ -196,6 +119,34 @@ fn issue9907() { //~^ bool_comparison // This is not part of the issue, but an unexpected found when fixing the issue, // the provided span was inside of macro rather than the macro callsite. - let _ = ((1 < 2) != m!(func)) as usize; + let _ = ((1 < 2) & !m!(func)) as usize; //~^ bool_comparison } + +#[allow(clippy::nonminimal_bool)] +fn issue15367() { + let a = true; + let b = false; + + // these cases are handled by `nonminimal_bool`, so don't double-lint + if a == !b {}; + if !a == b {}; + if b == !a {}; + if !b == a {}; +} + +fn issue15497() { + fn func() -> bool { + true + } + + fn foo(x: bool) -> bool { + x & !m!(func) + //~^ bool_comparison + } + + fn bar(x: bool) -> bool { + !x & m!(func) + //~^ bool_comparison + } +} diff --git a/src/tools/clippy/tests/ui/bool_comparison.rs b/src/tools/clippy/tests/ui/bool_comparison.rs index 7265c2b4c5a..1b1108d6ce5 100644 --- a/src/tools/clippy/tests/ui/bool_comparison.rs +++ b/src/tools/clippy/tests/ui/bool_comparison.rs @@ -1,97 +1,41 @@ #![allow(non_local_definitions, clippy::needless_if)] #![warn(clippy::bool_comparison)] -#![allow(clippy::non_canonical_partial_ord_impl, clippy::nonminimal_bool)] +#![allow(clippy::non_canonical_partial_ord_impl)] fn main() { let x = true; - if x == true { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x == false { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if true == x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if false == x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x != true { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x != false { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if true != x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if false != x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x < true { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if false < x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x > false { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if true > x { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if x == true { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x == false { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if true == x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if false == x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x != true { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x != false { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if true != x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if false != x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x < true { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if false < x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x > false { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if true > x { "yes" } else { "no" }; + //~^ bool_comparison + let y = true; - if x < y { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x > y { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if x < y { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x > y { "yes" } else { "no" }; + //~^ bool_comparison } -#[allow(dead_code)] fn issue3703() { struct Foo; impl PartialEq<bool> for Foo { @@ -127,26 +71,6 @@ fn issue3703() { if false < Foo {} } -#[allow(dead_code)] -fn issue4983() { - let a = true; - let b = false; - - if a == !b {}; - //~^ bool_comparison - if !a == b {}; - //~^ bool_comparison - if a == b {}; - if !a == !b {}; - - if b == !a {}; - //~^ bool_comparison - if !b == a {}; - //~^ bool_comparison - if b == a {}; - if !b == !a {}; -} - macro_rules! m { ($func:ident) => { $func() @@ -157,7 +81,6 @@ fn func() -> bool { true } -#[allow(dead_code)] fn issue3973() { // ok, don't lint on `cfg` invocation if false == cfg!(feature = "debugging") {} @@ -196,6 +119,34 @@ fn issue9907() { //~^ bool_comparison // This is not part of the issue, but an unexpected found when fixing the issue, // the provided span was inside of macro rather than the macro callsite. - let _ = ((1 < 2) == !m!(func)) as usize; + let _ = ((1 < 2) > m!(func)) as usize; //~^ bool_comparison } + +#[allow(clippy::nonminimal_bool)] +fn issue15367() { + let a = true; + let b = false; + + // these cases are handled by `nonminimal_bool`, so don't double-lint + if a == !b {}; + if !a == b {}; + if b == !a {}; + if !b == a {}; +} + +fn issue15497() { + fn func() -> bool { + true + } + + fn foo(x: bool) -> bool { + x > m!(func) + //~^ bool_comparison + } + + fn bar(x: bool) -> bool { + x < m!(func) + //~^ bool_comparison + } +} diff --git a/src/tools/clippy/tests/ui/bool_comparison.stderr b/src/tools/clippy/tests/ui/bool_comparison.stderr index ddbb9ff78ed..98881a2d20f 100644 --- a/src/tools/clippy/tests/ui/bool_comparison.stderr +++ b/src/tools/clippy/tests/ui/bool_comparison.stderr @@ -1,155 +1,143 @@ error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:7:8 + --> tests/ui/bool_comparison.rs:7:16 | -LL | if x == true { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if x == true { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` | = note: `-D clippy::bool-comparison` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:13:8 + --> tests/ui/bool_comparison.rs:9:16 | -LL | if x == false { - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if x == false { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `!x` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:19:8 + --> tests/ui/bool_comparison.rs:11:16 | -LL | if true == x { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if true == x { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:25:8 + --> tests/ui/bool_comparison.rs:13:16 | -LL | if false == x { - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if false == x { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `!x` error: inequality checks against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:31:8 + --> tests/ui/bool_comparison.rs:15:16 | -LL | if x != true { - | ^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if x != true { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `!x` error: inequality checks against false are unnecessary - --> tests/ui/bool_comparison.rs:37:8 + --> tests/ui/bool_comparison.rs:17:16 | -LL | if x != false { - | ^^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if x != false { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `x` error: inequality checks against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:43:8 + --> tests/ui/bool_comparison.rs:19:16 | -LL | if true != x { - | ^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if true != x { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `!x` error: inequality checks against false are unnecessary - --> tests/ui/bool_comparison.rs:49:8 + --> tests/ui/bool_comparison.rs:21:16 | -LL | if false != x { - | ^^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if false != x { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `x` error: less than comparison against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:55:8 + --> tests/ui/bool_comparison.rs:23:16 | -LL | if x < true { - | ^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if x < true { "yes" } else { "no" }; + | ^^^^^^^^ help: try: `!x` error: greater than checks against false are unnecessary - --> tests/ui/bool_comparison.rs:61:8 + --> tests/ui/bool_comparison.rs:25:16 | -LL | if false < x { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if false < x { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` error: greater than checks against false are unnecessary - --> tests/ui/bool_comparison.rs:67:8 + --> tests/ui/bool_comparison.rs:27:16 | -LL | if x > false { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if x > false { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` error: less than comparison against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:73:8 + --> tests/ui/bool_comparison.rs:29:16 | -LL | if true > x { - | ^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if true > x { "yes" } else { "no" }; + | ^^^^^^^^ help: try: `!x` error: order comparisons between booleans can be simplified - --> tests/ui/bool_comparison.rs:80:8 + --> tests/ui/bool_comparison.rs:33:16 | -LL | if x < y { - | ^^^^^ help: try simplifying it as shown: `!x & y` +LL | let _ = if x < y { "yes" } else { "no" }; + | ^^^^^ help: try: `!x & y` error: order comparisons between booleans can be simplified - --> tests/ui/bool_comparison.rs:86:8 + --> tests/ui/bool_comparison.rs:35:16 | -LL | if x > y { - | ^^^^^ help: try simplifying it as shown: `x & !y` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:135:8 - | -LL | if a == !b {}; - | ^^^^^^^ help: try simplifying it as shown: `a != b` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:137:8 - | -LL | if !a == b {}; - | ^^^^^^^ help: try simplifying it as shown: `a != b` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:142:8 - | -LL | if b == !a {}; - | ^^^^^^^ help: try simplifying it as shown: `b != a` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:144:8 - | -LL | if !b == a {}; - | ^^^^^^^ help: try simplifying it as shown: `b != a` +LL | let _ = if x > y { "yes" } else { "no" }; + | ^^^^^ help: try: `x & !y` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:169:8 + --> tests/ui/bool_comparison.rs:92:8 | LL | if false == m!(func) {} - | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + | ^^^^^^^^^^^^^^^^^ help: try: `!m!(func)` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:171:8 + --> tests/ui/bool_comparison.rs:94:8 | LL | if m!(func) == false {} - | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + | ^^^^^^^^^^^^^^^^^ help: try: `!m!(func)` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:173:8 + --> tests/ui/bool_comparison.rs:96:8 | LL | if true == m!(func) {} - | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` + | ^^^^^^^^^^^^^^^^ help: try: `m!(func)` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:175:8 + --> tests/ui/bool_comparison.rs:98:8 | LL | if m!(func) == true {} - | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` + | ^^^^^^^^^^^^^^^^ help: try: `m!(func)` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:193:14 + --> tests/ui/bool_comparison.rs:116:14 | LL | let _ = ((1 < 2) == false) as usize; - | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `1 >= 2` + | ^^^^^^^^^^^^^^^^ help: try: `1 >= 2` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:195:14 + --> tests/ui/bool_comparison.rs:118:14 | LL | let _ = (false == m!(func)) as usize; - | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + | ^^^^^^^^^^^^^^^^^ help: try: `!m!(func)` -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:199:14 +error: order comparisons between booleans can be simplified + --> tests/ui/bool_comparison.rs:122:14 + | +LL | let _ = ((1 < 2) > m!(func)) as usize; + | ^^^^^^^^^^^^^^^^^^ help: try: `(1 < 2) & !m!(func)` + +error: order comparisons between booleans can be simplified + --> tests/ui/bool_comparison.rs:144:9 + | +LL | x > m!(func) + | ^^^^^^^^^^^^ help: try: `x & !m!(func)` + +error: order comparisons between booleans can be simplified + --> tests/ui/bool_comparison.rs:149:9 | -LL | let _ = ((1 < 2) == !m!(func)) as usize; - | ^^^^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `(1 < 2) != m!(func)` +LL | x < m!(func) + | ^^^^^^^^^^^^ help: try: `!x & m!(func)` -error: aborting due to 25 previous errors +error: aborting due to 23 previous errors diff --git a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.fixed b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.fixed index bddcb0ebf64..3c1cf884595 100644 --- a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.fixed +++ b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.fixed @@ -1,30 +1,53 @@ #![warn(clippy::cast_slice_from_raw_parts)] -#[allow(unused_imports, unused_unsafe)] +const fn require_raw_slice_ptr<T>(_: *const [T]) {} + fn main() { let mut vec = vec![0u8; 1]; let ptr: *const u8 = vec.as_ptr(); let mptr = vec.as_mut_ptr(); - let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts(ptr, 1) }; //~^ cast_slice_from_raw_parts - let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts_mut(mptr, 1) }; //~^ cast_slice_from_raw_parts - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts { use core::slice; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts use slice as one; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts } { use std::slice; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts use slice as one; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts } + + // implicit cast + { + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::ptr::slice_from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::ptr::slice_from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = std::ptr::null(); + const MPTR: *mut u8 = std::ptr::null_mut(); + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::ptr::slice_from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::ptr::slice_from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; } diff --git a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.rs b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.rs index 0a1eb276d5e..8f57b1f9619 100644 --- a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.rs +++ b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.rs @@ -1,6 +1,7 @@ #![warn(clippy::cast_slice_from_raw_parts)] -#[allow(unused_imports, unused_unsafe)] +const fn require_raw_slice_ptr<T>(_: *const [T]) {} + fn main() { let mut vec = vec![0u8; 1]; let ptr: *const u8 = vec.as_ptr(); @@ -27,4 +28,26 @@ fn main() { let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; //~^ cast_slice_from_raw_parts } + + // implicit cast + { + let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = std::ptr::null(); + const MPTR: *mut u8 = std::ptr::null_mut(); + let _: *const [u8] = unsafe { std::slice::from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; } diff --git a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.stderr b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.stderr index 60794a988db..328dbafbafe 100644 --- a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.stderr +++ b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast.stderr @@ -1,47 +1,83 @@ error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:8:35 + --> tests/ui/cast_raw_slice_pointer_cast.rs:9:35 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) as *const [u8] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` | = note: `-D clippy::cast-slice-from-raw-parts` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_slice_from_raw_parts)]` error: casting the result of `from_raw_parts_mut` to *mut [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:10:35 + --> tests/ui/cast_raw_slice_pointer_cast.rs:11:35 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts_mut(mptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:12:26 + --> tests/ui/cast_raw_slice_pointer_cast.rs:13:26 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:16:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:17:30 | LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:19:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:20:30 | LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:24:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:25:30 | LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:27:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:28:30 | LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` -error: aborting due to 7 previous errors +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:34:39 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:36:37 + | +LL | let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:38:40 + | +LL | require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(ptr, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:46:39 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(PTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(PTR, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:48:37 + | +LL | let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(MPTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts_mut(MPTR, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:50:40 + | +LL | require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(PTR, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(PTR, 1)` + +error: aborting due to 13 previous errors diff --git a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.fixed b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.fixed new file mode 100644 index 00000000000..f71fb8d863c --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.fixed @@ -0,0 +1,55 @@ +#![warn(clippy::cast_slice_from_raw_parts)] +#![no_std] +#![crate_type = "lib"] + +const fn require_raw_slice_ptr<T>(_: *const [T]) {} + +fn main() { + let mut arr = [0u8; 1]; + let ptr: *const u8 = arr.as_ptr(); + let mptr = arr.as_mut_ptr(); + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + { + use core::slice; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + } + { + use core::slice; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + } + + // implicit cast + { + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = core::ptr::null(); + const MPTR: *mut u8 = core::ptr::null_mut(); + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::ptr::slice_from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; +} diff --git a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.rs b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.rs new file mode 100644 index 00000000000..743e44c97dc --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.rs @@ -0,0 +1,55 @@ +#![warn(clippy::cast_slice_from_raw_parts)] +#![no_std] +#![crate_type = "lib"] + +const fn require_raw_slice_ptr<T>(_: *const [T]) {} + +fn main() { + let mut arr = [0u8; 1]; + let ptr: *const u8 = arr.as_ptr(); + let mptr = arr.as_mut_ptr(); + let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) as *const [u8] }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + { + use core::slice; + let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + } + { + use core::slice; + let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + } + + // implicit cast + { + let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = core::ptr::null(); + const MPTR: *mut u8 = core::ptr::null_mut(); + let _: *const [u8] = unsafe { core::slice::from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; +} diff --git a/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.stderr b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.stderr new file mode 100644 index 00000000000..5488fbcfa1c --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_raw_slice_pointer_cast_no_std.stderr @@ -0,0 +1,83 @@ +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:11:35 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) as *const [u8] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | + = note: `-D clippy::cast-slice-from-raw-parts` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::cast_slice_from_raw_parts)]` + +error: casting the result of `from_raw_parts_mut` to *mut [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:13:35 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:15:26 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:19:30 + | +LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:22:30 + | +LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:27:30 + | +LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:30:30 + | +LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:36:39 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:38:37 + | +LL | let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:40:40 + | +LL | require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(ptr, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:48:39 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(PTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(PTR, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:50:37 + | +LL | let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(MPTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts_mut(MPTR, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:52:40 + | +LL | require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(PTR, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(PTR, 1)` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/collapsible_match.rs b/src/tools/clippy/tests/ui/collapsible_match.rs index 71b82040ff6..8931a3aa09c 100644 --- a/src/tools/clippy/tests/ui/collapsible_match.rs +++ b/src/tools/clippy/tests/ui/collapsible_match.rs @@ -316,6 +316,47 @@ fn lint_emitted_at_right_node(opt: Option<Result<u64, String>>) { }; } +pub fn issue_14155() { + let mut arr = ["a", "b", "c"]; + if let Some(last) = arr.last() { + match *last { + //~^ collapsible_match + "a" | "b" => { + unimplemented!() + }, + _ => (), + } + } + + if let Some(last) = arr.last() { + match &last { + //~^ collapsible_match + &&"a" | &&"b" => { + unimplemented!() + }, + _ => (), + } + } + + if let Some(mut last) = arr.last_mut() { + match &mut last { + //~^ collapsible_match + &mut &mut "a" | &mut &mut "b" => { + unimplemented!() + }, + _ => (), + } + } + + const NULL_PTR: *const &'static str = std::ptr::null(); + if let Some(last) = arr.last() { + match &raw const *last { + NULL_PTR => unimplemented!(), + _ => (), + } + } +} + fn make<T>() -> T { unimplemented!() } diff --git a/src/tools/clippy/tests/ui/collapsible_match.stderr b/src/tools/clippy/tests/ui/collapsible_match.stderr index c290d84ec29..14b1c1b187e 100644 --- a/src/tools/clippy/tests/ui/collapsible_match.stderr +++ b/src/tools/clippy/tests/ui/collapsible_match.stderr @@ -250,5 +250,74 @@ LL | if let Issue9647::A { a: Some(a), .. } = x { LL | if let Some(u) = a { | ^^^^^^^ with this pattern -error: aborting due to 13 previous errors +error: this `match` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:322:9 + | +LL | / match *last { +LL | | +LL | | "a" | "b" => { +LL | | unimplemented!() +LL | | }, +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:321:17 + | +LL | if let Some(last) = arr.last() { + | ^^^^ ---------- use: `arr.last().copied()` + | | + | replace this binding +... +LL | "a" | "b" => { + | ^^^^^^^^^ with this pattern + +error: this `match` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:332:9 + | +LL | / match &last { +LL | | +LL | | &&"a" | &&"b" => { +LL | | unimplemented!() +LL | | }, +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:331:17 + | +LL | if let Some(last) = arr.last() { + | ^^^^ ---------- use: `arr.last().as_ref()` + | | + | replace this binding +... +LL | &&"a" | &&"b" => { + | ^^^^^^^^^^^^^ with this pattern + +error: this `match` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:342:9 + | +LL | / match &mut last { +LL | | +LL | | &mut &mut "a" | &mut &mut "b" => { +LL | | unimplemented!() +LL | | }, +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:341:17 + | +LL | if let Some(mut last) = arr.last_mut() { + | ^^^^^^^^ -------------- use: `arr.last_mut().as_mut()` + | | + | replace this binding +... +LL | &mut &mut "a" | &mut &mut "b" => { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this pattern + +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/collapsible_match2.stderr b/src/tools/clippy/tests/ui/collapsible_match2.stderr index 7b273063752..25970670999 100644 --- a/src/tools/clippy/tests/ui/collapsible_match2.stderr +++ b/src/tools/clippy/tests/ui/collapsible_match2.stderr @@ -77,6 +77,8 @@ LL | | }, help: the outer pattern can be modified to include the inner pattern --> tests/ui/collapsible_match2.rs:54:14 | +LL | match Some(&[1]) { + | ---------- use: `Some(&[1]).copied()` LL | Some(s) => match *s { | ^ replace this binding LL | diff --git a/src/tools/clippy/tests/ui/derivable_impls.fixed b/src/tools/clippy/tests/ui/derivable_impls.fixed index 65bfded3883..9f9e4e253c3 100644 --- a/src/tools/clippy/tests/ui/derivable_impls.fixed +++ b/src/tools/clippy/tests/ui/derivable_impls.fixed @@ -1,4 +1,6 @@ #![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] use std::collections::HashMap; @@ -326,4 +328,40 @@ mod issue11368 { } } +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + struct Foo(u64); + + impl const Default for Foo { + fn default() -> Self { + Self(0) + } + } + + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl const Default for Bar { + fn default() -> Self { + Bar::A + } + } +} + +mod issue15536 { + #[derive(Copy, Clone)] + #[derive(Default)] + enum Bar { + #[default] + A, + B, + } + + +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/derivable_impls.rs b/src/tools/clippy/tests/ui/derivable_impls.rs index 4826c5497b4..74a793b9a70 100644 --- a/src/tools/clippy/tests/ui/derivable_impls.rs +++ b/src/tools/clippy/tests/ui/derivable_impls.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] use std::collections::HashMap; @@ -396,4 +398,43 @@ mod issue11368 { } } +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + struct Foo(u64); + + impl const Default for Foo { + fn default() -> Self { + Self(0) + } + } + + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl const Default for Bar { + fn default() -> Self { + Bar::A + } + } +} + +mod issue15536 { + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl Default for Bar { + //~^ derivable_impls + fn default() -> Self { + Self::A + } + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/derivable_impls.stderr b/src/tools/clippy/tests/ui/derivable_impls.stderr index 0f73ad55a85..cd46414cb4a 100644 --- a/src/tools/clippy/tests/ui/derivable_impls.stderr +++ b/src/tools/clippy/tests/ui/derivable_impls.stderr @@ -1,5 +1,5 @@ error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:20:1 + --> tests/ui/derivable_impls.rs:22:1 | LL | / impl std::default::Default for FooDefault<'_> { LL | | @@ -18,7 +18,7 @@ LL ~ struct FooDefault<'a> { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:42:1 + --> tests/ui/derivable_impls.rs:44:1 | LL | / impl std::default::Default for TupleDefault { LL | | @@ -35,7 +35,7 @@ LL ~ struct TupleDefault(bool, i32, u64); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:95:1 + --> tests/ui/derivable_impls.rs:97:1 | LL | / impl Default for StrDefault<'_> { LL | | @@ -52,7 +52,7 @@ LL ~ struct StrDefault<'a>(&'a str); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:122:1 + --> tests/ui/derivable_impls.rs:124:1 | LL | / impl Default for Y { LL | | @@ -69,7 +69,7 @@ LL ~ struct Y(u32); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:162:1 + --> tests/ui/derivable_impls.rs:164:1 | LL | / impl Default for WithoutSelfCurly { LL | | @@ -86,7 +86,7 @@ LL ~ struct WithoutSelfCurly { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:171:1 + --> tests/ui/derivable_impls.rs:173:1 | LL | / impl Default for WithoutSelfParan { LL | | @@ -103,7 +103,7 @@ LL ~ struct WithoutSelfParan(bool); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:194:1 + --> tests/ui/derivable_impls.rs:196:1 | LL | / impl Default for DirectDefaultDefaultCall { LL | | @@ -119,7 +119,7 @@ LL ~ pub struct DirectDefaultDefaultCall { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:206:1 + --> tests/ui/derivable_impls.rs:208:1 | LL | / impl Default for EquivalentToDefaultDefaultCallVec { LL | | @@ -135,7 +135,7 @@ LL ~ pub struct EquivalentToDefaultDefaultCallVec { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:234:1 + --> tests/ui/derivable_impls.rs:236:1 | LL | / impl Default for EquivalentToDefaultDefaultCallLocal { LL | | @@ -151,7 +151,7 @@ LL ~ pub struct EquivalentToDefaultDefaultCallLocal { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:274:1 + --> tests/ui/derivable_impls.rs:276:1 | LL | / impl Default for RepeatDefault1 { LL | | @@ -168,7 +168,7 @@ LL ~ pub struct RepeatDefault1 { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:309:1 + --> tests/ui/derivable_impls.rs:311:1 | LL | / impl Default for SimpleEnum { LL | | @@ -187,5 +187,28 @@ LL ~ #[default] LL ~ Bar, | -error: aborting due to 11 previous errors +error: this `impl` can be derived + --> tests/ui/derivable_impls.rs:432:5 + | +LL | / impl Default for Bar { +LL | | +LL | | fn default() -> Self { +LL | | Self::A +LL | | } +LL | | } + | |_____^ + | +help: replace the manual implementation with a derive attribute and mark the default variant + | +LL ~ #[derive(Default)] +LL ~ enum Bar { +LL ~ #[default] +LL ~ A, +LL | B, +LL | } +LL | +LL ~ + | + +error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/derivable_impls_derive_const.fixed b/src/tools/clippy/tests/ui/derivable_impls_derive_const.fixed new file mode 100644 index 00000000000..f0d8d2d2409 --- /dev/null +++ b/src/tools/clippy/tests/ui/derivable_impls_derive_const.fixed @@ -0,0 +1,25 @@ +#![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] +#![feature(derive_const)] + +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + #[derive_const(Default)] + struct Foo(u64); + + + + #[derive(Copy, Clone)] + #[derive_const(Default)] + enum Bar { + #[default] + A, + B, + } + + +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derivable_impls_derive_const.rs b/src/tools/clippy/tests/ui/derivable_impls_derive_const.rs new file mode 100644 index 00000000000..7d70db1c097 --- /dev/null +++ b/src/tools/clippy/tests/ui/derivable_impls_derive_const.rs @@ -0,0 +1,32 @@ +#![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] +#![feature(derive_const)] + +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + struct Foo(u64); + + impl const Default for Foo { + //~^ derivable_impls + fn default() -> Self { + Self(0) + } + } + + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl const Default for Bar { + //~^ derivable_impls + fn default() -> Self { + Bar::A + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derivable_impls_derive_const.stderr b/src/tools/clippy/tests/ui/derivable_impls_derive_const.stderr new file mode 100644 index 00000000000..196bac185dd --- /dev/null +++ b/src/tools/clippy/tests/ui/derivable_impls_derive_const.stderr @@ -0,0 +1,46 @@ +error: this `impl` can be derived + --> tests/ui/derivable_impls_derive_const.rs:11:5 + | +LL | / impl const Default for Foo { +LL | | +LL | | fn default() -> Self { +LL | | Self(0) +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::derivable-impls` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::derivable_impls)]` +help: replace the manual implementation with a derive attribute + | +LL ~ #[derive_const(Default)] +LL ~ struct Foo(u64); +LL | +LL ~ + | + +error: this `impl` can be derived + --> tests/ui/derivable_impls_derive_const.rs:24:5 + | +LL | / impl const Default for Bar { +LL | | +LL | | fn default() -> Self { +LL | | Bar::A +LL | | } +LL | | } + | |_____^ + | +help: replace the manual implementation with a derive attribute and mark the default variant + | +LL ~ #[derive_const(Default)] +LL ~ enum Bar { +LL ~ #[default] +LL ~ A, +LL | B, +LL | } +LL | +LL ~ + | + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed index 423a73734da..46695dc929a 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed @@ -73,6 +73,7 @@ fn test_units() { /// GPLv2 GPLv3 /// GitHub GitLab /// IPv4 IPv6 +/// InfiniBand RoCE /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript /// PowerPC WebAssembly /// NaN NaNs diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.rs b/src/tools/clippy/tests/ui/doc/doc-fixable.rs index 8deffb4210e..4082fa5b56f 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.rs +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.rs @@ -73,6 +73,7 @@ fn test_units() { /// GPLv2 GPLv3 /// GitHub GitLab /// IPv4 IPv6 +/// InfiniBand RoCE /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript /// PowerPC WebAssembly /// NaN NaNs diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.stderr b/src/tools/clippy/tests/ui/doc/doc-fixable.stderr index 98c26e6bec2..2a94a8f3165 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.stderr +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.stderr @@ -145,7 +145,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:90:5 + --> tests/ui/doc/doc-fixable.rs:91:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:108:5 + --> tests/ui/doc/doc-fixable.rs:109:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:117:8 + --> tests/ui/doc/doc-fixable.rs:118:8 | LL | /// ## CamelCaseThing | ^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + /// ## `CamelCaseThing` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:121:7 + --> tests/ui/doc/doc-fixable.rs:122:7 | LL | /// # CamelCaseThing | ^^^^^^^^^^^^^^ @@ -193,7 +193,7 @@ LL + /// # `CamelCaseThing` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:124:22 + --> tests/ui/doc/doc-fixable.rs:125:22 | LL | /// Not a title #897 CamelCaseThing | ^^^^^^^^^^^^^^ @@ -205,7 +205,7 @@ LL + /// Not a title #897 `CamelCaseThing` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:126:5 + --> tests/ui/doc/doc-fixable.rs:127:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +217,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:134:5 + --> tests/ui/doc/doc-fixable.rs:135:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -229,7 +229,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:148:5 + --> tests/ui/doc/doc-fixable.rs:149:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -241,7 +241,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:160:43 + --> tests/ui/doc/doc-fixable.rs:161:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -253,7 +253,7 @@ LL + /** E.g., serialization of an empty list: `FooBar` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:165:5 + --> tests/ui/doc/doc-fixable.rs:166:5 | LL | And BarQuz too. | ^^^^^^ @@ -265,7 +265,7 @@ LL + And `BarQuz` too. | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:166:1 + --> tests/ui/doc/doc-fixable.rs:167:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -277,7 +277,7 @@ LL + `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:174:43 + --> tests/ui/doc/doc-fixable.rs:175:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -289,7 +289,7 @@ LL + /** E.g., serialization of an empty list: `FooBar` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:179:5 + --> tests/ui/doc/doc-fixable.rs:180:5 | LL | And BarQuz too. | ^^^^^^ @@ -301,7 +301,7 @@ LL + And `BarQuz` too. | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:180:1 + --> tests/ui/doc/doc-fixable.rs:181:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -313,7 +313,7 @@ LL + `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:194:5 + --> tests/ui/doc/doc-fixable.rs:195:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -325,7 +325,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:214:22 + --> tests/ui/doc/doc-fixable.rs:215:22 | LL | /// An iterator over mycrate::Collection's values. | ^^^^^^^^^^^^^^^^^^^ @@ -337,7 +337,7 @@ LL + /// An iterator over `mycrate::Collection`'s values. | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:239:34 + --> tests/ui/doc/doc-fixable.rs:240:34 | LL | /// Foo \[bar\] \[baz\] \[qux\]. DocMarkdownLint | ^^^^^^^^^^^^^^^ @@ -349,7 +349,7 @@ LL + /// Foo \[bar\] \[baz\] \[qux\]. `DocMarkdownLint` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:263:22 + --> tests/ui/doc/doc-fixable.rs:264:22 | LL | /// There is no try (do() or do_not()). | ^^^^ @@ -361,7 +361,7 @@ LL + /// There is no try (`do()` or do_not()). | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:263:30 + --> tests/ui/doc/doc-fixable.rs:264:30 | LL | /// There is no try (do() or do_not()). | ^^^^^^^^ @@ -373,7 +373,7 @@ LL + /// There is no try (do() or `do_not()`). | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:268:5 + --> tests/ui/doc/doc-fixable.rs:269:5 | LL | /// ABes | ^^^^ @@ -385,7 +385,7 @@ LL + /// `ABes` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:275:9 + --> tests/ui/doc/doc-fixable.rs:276:9 | LL | /// foo() | ^^^^^ @@ -397,7 +397,7 @@ LL + /// `foo()` | error: you should put bare URLs between `<`/`>` or make a proper Markdown link - --> tests/ui/doc/doc-fixable.rs:280:5 + --> tests/ui/doc/doc-fixable.rs:281:5 | LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `<https://github.com/rust-lang/rust-clippy/pull/12836>` diff --git a/src/tools/clippy/tests/ui/entry_unfixable.stderr b/src/tools/clippy/tests/ui/entry_unfixable.stderr index 0197d2ab4cf..f92f472512f 100644 --- a/src/tools/clippy/tests/ui/entry_unfixable.stderr +++ b/src/tools/clippy/tests/ui/entry_unfixable.stderr @@ -10,6 +10,7 @@ LL | | false LL | | } | |_____________^ | + = help: consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.HashMap.html#entry-api = note: `-D clippy::map-entry` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::map_entry)]` @@ -24,6 +25,8 @@ LL | | } else { LL | | hm.insert(key, true); LL | | } | |_____^ + | + = help: consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.HashMap.html#entry-api error: usage of `contains_key` followed by `insert` on a `HashMap` --> tests/ui/entry_unfixable.rs:80:13 @@ -36,6 +39,8 @@ LL | | let interner = INTERNER.lock().unwrap(); LL | | return Err(interner.resolve(name).unwrap().to_owned()); LL | | } | |_____________^ + | + = help: consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.HashMap.html#entry-api error: aborting due to 3 previous errors diff --git a/src/tools/clippy/tests/ui/eta.fixed b/src/tools/clippy/tests/ui/eta.fixed index 3d2b41b8fb8..6944a979c05 100644 --- a/src/tools/clippy/tests/ui/eta.fixed +++ b/src/tools/clippy/tests/ui/eta.fixed @@ -565,6 +565,51 @@ fn issue_14789() { ); } +fn issue_15072() { + use std::ops::Deref; + + struct Foo; + impl Deref for Foo { + type Target = fn() -> &'static str; + + fn deref(&self) -> &Self::Target { + fn hello() -> &'static str { + "Hello, world!" + } + &(hello as fn() -> &'static str) + } + } + + fn accepts_fn(f: impl Fn() -> &'static str) { + println!("{}", f()); + } + + fn some_fn() -> &'static str { + todo!() + } + + let f = &Foo; + accepts_fn(**f); + //~^ redundant_closure + + let g = &some_fn; + accepts_fn(g); + //~^ redundant_closure + + struct Bar(Foo); + impl Deref for Bar { + type Target = Foo; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let b = &Bar(Foo); + accepts_fn(***b); + //~^ redundant_closure +} + fn issue8817() { fn f(_: u32) -> u32 { todo!() diff --git a/src/tools/clippy/tests/ui/eta.rs b/src/tools/clippy/tests/ui/eta.rs index 79d1103410d..5bcc1cb26fd 100644 --- a/src/tools/clippy/tests/ui/eta.rs +++ b/src/tools/clippy/tests/ui/eta.rs @@ -565,6 +565,51 @@ fn issue_14789() { ); } +fn issue_15072() { + use std::ops::Deref; + + struct Foo; + impl Deref for Foo { + type Target = fn() -> &'static str; + + fn deref(&self) -> &Self::Target { + fn hello() -> &'static str { + "Hello, world!" + } + &(hello as fn() -> &'static str) + } + } + + fn accepts_fn(f: impl Fn() -> &'static str) { + println!("{}", f()); + } + + fn some_fn() -> &'static str { + todo!() + } + + let f = &Foo; + accepts_fn(|| f()); + //~^ redundant_closure + + let g = &some_fn; + accepts_fn(|| g()); + //~^ redundant_closure + + struct Bar(Foo); + impl Deref for Bar { + type Target = Foo; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let b = &Bar(Foo); + accepts_fn(|| b()); + //~^ redundant_closure +} + fn issue8817() { fn f(_: u32) -> u32 { todo!() diff --git a/src/tools/clippy/tests/ui/eta.stderr b/src/tools/clippy/tests/ui/eta.stderr index aa32ed1a38e..0b401cdea98 100644 --- a/src/tools/clippy/tests/ui/eta.stderr +++ b/src/tools/clippy/tests/ui/eta.stderr @@ -215,28 +215,46 @@ LL | let _field = bind.or_else(|| get_default()).unwrap(); | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `get_default` error: redundant closure - --> tests/ui/eta.rs:588:14 + --> tests/ui/eta.rs:592:16 + | +LL | accepts_fn(|| f()); + | ^^^^^^ help: replace the closure with the function itself: `**f` + +error: redundant closure + --> tests/ui/eta.rs:596:16 + | +LL | accepts_fn(|| g()); + | ^^^^^^ help: replace the closure with the function itself: `g` + +error: redundant closure + --> tests/ui/eta.rs:609:16 + | +LL | accepts_fn(|| b()); + | ^^^^^^ help: replace the closure with the function itself: `***b` + +error: redundant closure + --> tests/ui/eta.rs:633:14 | LL | .map(|n| MyError::A(n)) | ^^^^^^^^^^^^^^^^^ help: replace the closure with the tuple variant itself: `MyError::A` error: redundant closure - --> tests/ui/eta.rs:585:14 + --> tests/ui/eta.rs:630:14 | LL | .map(|n| S(n)) | ^^^^^^^^ help: replace the closure with the tuple struct itself: `S` error: redundant closure - --> tests/ui/eta.rs:582:14 + --> tests/ui/eta.rs:627:14 | LL | .map(|n| g(n)) | ^^^^^^^^ help: replace the closure with the function itself: `g` error: redundant closure - --> tests/ui/eta.rs:579:14 + --> tests/ui/eta.rs:624:14 | LL | .map(|n| f(n)) | ^^^^^^^^ help: replace the closure with the function itself: `f` -error: aborting due to 39 previous errors +error: aborting due to 42 previous errors diff --git a/src/tools/clippy/tests/ui/excessive_precision.fixed b/src/tools/clippy/tests/ui/excessive_precision.fixed index 8a8c2e1939c..8158d4b332a 100644 --- a/src/tools/clippy/tests/ui/excessive_precision.fixed +++ b/src/tools/clippy/tests/ui/excessive_precision.fixed @@ -4,9 +4,16 @@ overflowing_literals, unused_variables, clippy::print_literal, - clippy::useless_vec + clippy::useless_vec, + clippy::approx_constant )] +macro_rules! make_pi { + ($i:ident : $t:ty) => { + const $i: $t = 3.14159265358979323846264338327950288419716939937510582097494459230781640628; + }; +} + fn main() { // Consts const GOOD32: f32 = 0.123_456; @@ -101,4 +108,40 @@ fn main() { const _: f64 = 3.0; //~^ excessive_precision const _: f64 = 3.0000000000000000; + + // Overly specified constants + let _: f32 = 1.012_345_7; + //~^ excessive_precision + let _: f64 = 1.012_345_678_901_234_6; + //~^ excessive_precision + const _: f32 = 1.01234567890123456789012345678901234567890; + const _: f64 = 1.01234567890123456789012345678901234567890; + + static STATIC1: f32 = 1.01234567890123456789012345678901234567890; + static STATIC2: f64 = 1.01234567890123456789012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.01234567890123456789012345678901234567890; + static mut STATIC_MUT2: f64 = 1.01234567890123456789012345678901234567890; + + // From issue #13855 + let gamma = 0.577_215_664_901_532_9; + //~^ excessive_precision + const GAMMA: f64 = 0.5772156649015328606065120900824024310421; + + make_pi!(P32: f32); + make_pi!(P64: f64); +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; } diff --git a/src/tools/clippy/tests/ui/excessive_precision.rs b/src/tools/clippy/tests/ui/excessive_precision.rs index 5dcf55cb927..7ee6247ee5a 100644 --- a/src/tools/clippy/tests/ui/excessive_precision.rs +++ b/src/tools/clippy/tests/ui/excessive_precision.rs @@ -4,9 +4,16 @@ overflowing_literals, unused_variables, clippy::print_literal, - clippy::useless_vec + clippy::useless_vec, + clippy::approx_constant )] +macro_rules! make_pi { + ($i:ident : $t:ty) => { + const $i: $t = 3.14159265358979323846264338327950288419716939937510582097494459230781640628; + }; +} + fn main() { // Consts const GOOD32: f32 = 0.123_456; @@ -101,4 +108,40 @@ fn main() { const _: f64 = 3.0000000000000000e+00; //~^ excessive_precision const _: f64 = 3.0000000000000000; + + // Overly specified constants + let _: f32 = 1.01234567890123456789012345678901234567890; + //~^ excessive_precision + let _: f64 = 1.01234567890123456789012345678901234567890; + //~^ excessive_precision + const _: f32 = 1.01234567890123456789012345678901234567890; + const _: f64 = 1.01234567890123456789012345678901234567890; + + static STATIC1: f32 = 1.01234567890123456789012345678901234567890; + static STATIC2: f64 = 1.01234567890123456789012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.01234567890123456789012345678901234567890; + static mut STATIC_MUT2: f64 = 1.01234567890123456789012345678901234567890; + + // From issue #13855 + let gamma = 0.5772156649015328606065120900824024310421; + //~^ excessive_precision + const GAMMA: f64 = 0.5772156649015328606065120900824024310421; + + make_pi!(P32: f32); + make_pi!(P64: f64); +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; } diff --git a/src/tools/clippy/tests/ui/excessive_precision.stderr b/src/tools/clippy/tests/ui/excessive_precision.stderr index f5eeadf0c8c..40806d67487 100644 --- a/src/tools/clippy/tests/ui/excessive_precision.stderr +++ b/src/tools/clippy/tests/ui/excessive_precision.stderr @@ -1,5 +1,5 @@ error: float has excessive precision - --> tests/ui/excessive_precision.rs:20:26 + --> tests/ui/excessive_precision.rs:27:26 | LL | const BAD32_1: f32 = 0.123_456_789_f32; | ^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + const BAD32_1: f32 = 0.123_456_79_f32; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:22:26 + --> tests/ui/excessive_precision.rs:29:26 | LL | const BAD32_2: f32 = 0.123_456_789; | ^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + const BAD32_2: f32 = 0.123_456_79; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:24:26 + --> tests/ui/excessive_precision.rs:31:26 | LL | const BAD32_3: f32 = 0.100_000_000_000_1; | ^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + const BAD32_3: f32 = 0.1; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:26:29 + --> tests/ui/excessive_precision.rs:33:29 | LL | const BAD32_EDGE: f32 = 1.000_000_9; | ^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + const BAD32_EDGE: f32 = 1.000_001; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:31:26 + --> tests/ui/excessive_precision.rs:38:26 | LL | const BAD64_3: f64 = 0.100_000_000_000_000_000_1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + const BAD64_3: f64 = 0.1; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:35:22 + --> tests/ui/excessive_precision.rs:42:22 | LL | println!("{:?}", 8.888_888_888_888_888_888_888); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + println!("{:?}", 8.888_888_888_888_89); | error: float has excessive precision - --> tests/ui/excessive_precision.rs:47:22 + --> tests/ui/excessive_precision.rs:54:22 | LL | let bad32: f32 = 1.123_456_789; | ^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + let bad32: f32 = 1.123_456_8; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:49:26 + --> tests/ui/excessive_precision.rs:56:26 | LL | let bad32_suf: f32 = 1.123_456_789_f32; | ^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL + let bad32_suf: f32 = 1.123_456_8_f32; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:51:21 + --> tests/ui/excessive_precision.rs:58:21 | LL | let bad32_inf = 1.123_456_789_f32; | ^^^^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL + let bad32_inf = 1.123_456_8_f32; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:62:36 + --> tests/ui/excessive_precision.rs:69:36 | LL | let bad_vec32: Vec<f32> = vec![0.123_456_789]; | ^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL + let bad_vec32: Vec<f32> = vec![0.123_456_79]; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:64:36 + --> tests/ui/excessive_precision.rs:71:36 | LL | let bad_vec64: Vec<f64> = vec![0.123_456_789_123_456_789]; | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL + let bad_vec64: Vec<f64> = vec![0.123_456_789_123_456_78]; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:69:24 + --> tests/ui/excessive_precision.rs:76:24 | LL | let bad_e32: f32 = 1.123_456_788_888e-10; | ^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL + let bad_e32: f32 = 1.123_456_8e-10; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:73:27 + --> tests/ui/excessive_precision.rs:80:27 | LL | let bad_bige32: f32 = 1.123_456_788_888E-10; | ^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + let bad_bige32: f32 = 1.123_456_8E-10; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:86:13 + --> tests/ui/excessive_precision.rs:93:13 | LL | let _ = 2.225_073_858_507_201_1e-308_f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + let _ = 2.225_073_858_507_201e-308_f64; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:90:13 + --> tests/ui/excessive_precision.rs:97:13 | LL | let _ = 1.000_000_000_000_001e-324_f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + let _ = 0_f64; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:101:20 + --> tests/ui/excessive_precision.rs:108:20 | LL | const _: f64 = 3.0000000000000000e+00; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -192,5 +192,56 @@ LL - const _: f64 = 3.0000000000000000e+00; LL + const _: f64 = 3.0; | -error: aborting due to 16 previous errors +error: float has excessive precision + --> tests/ui/excessive_precision.rs:113:18 + | +LL | let _: f32 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui/excessive_precision.rs:113:5 + | +LL | let _: f32 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let _: f32 = 1.01234567890123456789012345678901234567890; +LL + let _: f32 = 1.012_345_7; + | + +error: float has excessive precision + --> tests/ui/excessive_precision.rs:115:18 + | +LL | let _: f64 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui/excessive_precision.rs:115:5 + | +LL | let _: f64 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let _: f64 = 1.01234567890123456789012345678901234567890; +LL + let _: f64 = 1.012_345_678_901_234_6; + | + +error: float has excessive precision + --> tests/ui/excessive_precision.rs:127:17 + | +LL | let gamma = 0.5772156649015328606065120900824024310421; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui/excessive_precision.rs:127:5 + | +LL | let gamma = 0.5772156649015328606065120900824024310421; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let gamma = 0.5772156649015328606065120900824024310421; +LL + let gamma = 0.577_215_664_901_532_9; + | + +error: aborting due to 19 previous errors diff --git a/src/tools/clippy/tests/ui/float_equality_without_abs.rs b/src/tools/clippy/tests/ui/float_equality_without_abs.rs index a1548db6710..e1dd7902683 100644 --- a/src/tools/clippy/tests/ui/float_equality_without_abs.rs +++ b/src/tools/clippy/tests/ui/float_equality_without_abs.rs @@ -1,8 +1,8 @@ +#![feature(f128)] +#![feature(f16)] #![warn(clippy::float_equality_without_abs)] //@no-rustfix: suggestions cause type ambiguity -// FIXME(f16_f128): add tests for these types when abs is available - pub fn is_roughly_equal(a: f32, b: f32) -> bool { (a - b) < f32::EPSILON //~^ float_equality_without_abs @@ -44,10 +44,20 @@ pub fn main() { let _ = f32::EPSILON > 1.0 - 2.0; //~^ float_equality_without_abs + let _ = (a as f16 - b as f16) < f16::EPSILON; + //~^ float_equality_without_abs + + let _ = (a as f128 - b as f128) < f128::EPSILON; + //~^ float_equality_without_abs + // those are correct + let _ = (a as f16 - b as f16).abs() < f16::EPSILON; let _ = (a - b).abs() < f32::EPSILON; let _ = (a as f64 - b as f64).abs() < f64::EPSILON; + let _ = (a as f128 - b as f128).abs() < f128::EPSILON; + let _ = f16::EPSILON > (a as f16 - b as f16).abs(); let _ = f32::EPSILON > (a - b).abs(); let _ = f64::EPSILON > (a as f64 - b as f64).abs(); + let _ = f128::EPSILON > (a as f128 - b as f128).abs(); } diff --git a/src/tools/clippy/tests/ui/float_equality_without_abs.stderr b/src/tools/clippy/tests/ui/float_equality_without_abs.stderr index d4c89ce72ba..55a150dead5 100644 --- a/src/tools/clippy/tests/ui/float_equality_without_abs.stderr +++ b/src/tools/clippy/tests/ui/float_equality_without_abs.stderr @@ -89,5 +89,21 @@ LL | let _ = f32::EPSILON > 1.0 - 2.0; | | | help: add `.abs()`: `(1.0 - 2.0).abs()` -error: aborting due to 11 previous errors +error: float equality check without `.abs()` + --> tests/ui/float_equality_without_abs.rs:47:13 + | +LL | let _ = (a as f16 - b as f16) < f16::EPSILON; + | ---------------------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a as f16 - b as f16).abs()` + +error: float equality check without `.abs()` + --> tests/ui/float_equality_without_abs.rs:50:13 + | +LL | let _ = (a as f128 - b as f128) < f128::EPSILON; + | -----------------------^^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a as f128 - b as f128).abs()` + +error: aborting due to 13 previous errors diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none.fixed b/src/tools/clippy/tests/ui/if_then_some_else_none.fixed index d14a805b666..0fd130609ae 100644 --- a/src/tools/clippy/tests/ui/if_then_some_else_none.fixed +++ b/src/tools/clippy/tests/ui/if_then_some_else_none.fixed @@ -165,3 +165,44 @@ mod issue15257 { do_something((i % 2 == 0).then_some(closure_fn)); } } + +fn issue15005() { + struct Counter { + count: u32, + } + + impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } + } + + impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option<Self::Item> { + //~v if_then_some_else_none + (self.count < 5).then(|| { self.count += 1; self.count }) + } + } +} + +fn statements_from_macro() { + macro_rules! mac { + () => { + println!("foo"); + println!("bar"); + }; + } + //~v if_then_some_else_none + let _ = true.then(|| { mac!(); 42 }); +} + +fn dont_lint_inside_macros() { + macro_rules! mac { + ($cond:expr, $res:expr) => { + if $cond { Some($res) } else { None } + }; + } + let _: Option<u32> = mac!(true, 42); +} diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none.rs b/src/tools/clippy/tests/ui/if_then_some_else_none.rs index bb0072f3157..640828aa9bf 100644 --- a/src/tools/clippy/tests/ui/if_then_some_else_none.rs +++ b/src/tools/clippy/tests/ui/if_then_some_else_none.rs @@ -211,3 +211,54 @@ mod issue15257 { }); } } + +fn issue15005() { + struct Counter { + count: u32, + } + + impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } + } + + impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option<Self::Item> { + //~v if_then_some_else_none + if self.count < 5 { + self.count += 1; + Some(self.count) + } else { + None + } + } + } +} + +fn statements_from_macro() { + macro_rules! mac { + () => { + println!("foo"); + println!("bar"); + }; + } + //~v if_then_some_else_none + let _ = if true { + mac!(); + Some(42) + } else { + None + }; +} + +fn dont_lint_inside_macros() { + macro_rules! mac { + ($cond:expr, $res:expr) => { + if $cond { Some($res) } else { None } + }; + } + let _: Option<u32> = mac!(true, 42); +} diff --git a/src/tools/clippy/tests/ui/if_then_some_else_none.stderr b/src/tools/clippy/tests/ui/if_then_some_else_none.stderr index c2e624a0a73..58651a05594 100644 --- a/src/tools/clippy/tests/ui/if_then_some_else_none.stderr +++ b/src/tools/clippy/tests/ui/if_then_some_else_none.stderr @@ -116,5 +116,28 @@ LL | | None LL | | }); | |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)` -error: aborting due to 11 previous errors +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:231:13 + | +LL | / if self.count < 5 { +LL | | self.count += 1; +LL | | Some(self.count) +LL | | } else { +LL | | None +LL | | } + | |_____________^ help: try: `(self.count < 5).then(|| { self.count += 1; self.count })` + +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:249:13 + | +LL | let _ = if true { + | _____________^ +LL | | mac!(); +LL | | Some(42) +LL | | } else { +LL | | None +LL | | }; + | |_____^ help: try: `true.then(|| { mac!(); 42 })` + +error: aborting due to 13 previous errors diff --git a/src/tools/clippy/tests/ui/infinite_loops.rs b/src/tools/clippy/tests/ui/infinite_loops.rs index 9b8c3933197..7d01a7fb61f 100644 --- a/src/tools/clippy/tests/ui/infinite_loops.rs +++ b/src/tools/clippy/tests/ui/infinite_loops.rs @@ -521,4 +521,19 @@ mod tokio_spawn_test { } } +mod issue15541 { + async fn good() -> ! { + loop { + std::future::pending().await + } + } + + async fn bad() { + //~v infinite_loop + loop { + std::future::pending().await + } + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/infinite_loops.stderr b/src/tools/clippy/tests/ui/infinite_loops.stderr index 4c6b6f725f1..319f1e5012b 100644 --- a/src/tools/clippy/tests/ui/infinite_loops.stderr +++ b/src/tools/clippy/tests/ui/infinite_loops.stderr @@ -333,5 +333,15 @@ LL | | } | = help: if this is not intended, try adding a `break` or `return` condition in the loop -error: aborting due to 23 previous errors +error: infinite loop detected + --> tests/ui/infinite_loops.rs:533:9 + | +LL | / loop { +LL | | std::future::pending().await +LL | | } + | |_________^ + | + = help: if this is not intended, try adding a `break` or `return` condition in the loop + +error: aborting due to 24 previous errors diff --git a/src/tools/clippy/tests/ui/manual_is_ascii_check.fixed b/src/tools/clippy/tests/ui/manual_is_ascii_check.fixed index 7b0d1906834..406336dbd09 100644 --- a/src/tools/clippy/tests/ui/manual_is_ascii_check.fixed +++ b/src/tools/clippy/tests/ui/manual_is_ascii_check.fixed @@ -108,6 +108,8 @@ fn generics() { //~^ manual_is_ascii_check take_while(|c: char| c.is_ascii_uppercase()); //~^ manual_is_ascii_check + take_while(|c: char| c.is_ascii_uppercase()); + //~^ manual_is_ascii_check } fn adds_type_reference() { @@ -115,4 +117,6 @@ fn adds_type_reference() { //~^ manual_is_ascii_check let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); //~^ manual_is_ascii_check + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); + //~^ manual_is_ascii_check } diff --git a/src/tools/clippy/tests/ui/manual_is_ascii_check.rs b/src/tools/clippy/tests/ui/manual_is_ascii_check.rs index e4f7fe9f583..a624497e75e 100644 --- a/src/tools/clippy/tests/ui/manual_is_ascii_check.rs +++ b/src/tools/clippy/tests/ui/manual_is_ascii_check.rs @@ -108,6 +108,8 @@ fn generics() { //~^ manual_is_ascii_check take_while(|c: char| ('A'..='Z').contains(&c)); //~^ manual_is_ascii_check + take_while(|c| matches!(c, 'A'..='Z')); + //~^ manual_is_ascii_check } fn adds_type_reference() { @@ -115,4 +117,6 @@ fn adds_type_reference() { //~^ manual_is_ascii_check let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| ('0'..='9').contains(c)).collect(); //~^ manual_is_ascii_check + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| matches!(c, '0'..='9')).collect(); + //~^ manual_is_ascii_check } diff --git a/src/tools/clippy/tests/ui/manual_is_ascii_check.stderr b/src/tools/clippy/tests/ui/manual_is_ascii_check.stderr index 9fd7f457b42..cb2548ea731 100644 --- a/src/tools/clippy/tests/ui/manual_is_ascii_check.stderr +++ b/src/tools/clippy/tests/ui/manual_is_ascii_check.stderr @@ -176,7 +176,19 @@ LL | take_while(|c: char| ('A'..='Z').contains(&c)); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `c.is_ascii_uppercase()` error: manual check for common ascii range - --> tests/ui/manual_is_ascii_check.rs:114:63 + --> tests/ui/manual_is_ascii_check.rs:111:20 + | +LL | take_while(|c| matches!(c, 'A'..='Z')); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - take_while(|c| matches!(c, 'A'..='Z')); +LL + take_while(|c: char| c.is_ascii_uppercase()); + | + +error: manual check for common ascii range + --> tests/ui/manual_is_ascii_check.rs:116:63 | LL | let digits: Vec<&char> = ['1', 'A'].iter().take_while(|c| ('0'..='9').contains(c)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -188,7 +200,7 @@ LL + let digits: Vec<&char> = ['1', 'A'].iter().take_while(|c: &&char| c.is_ | error: manual check for common ascii range - --> tests/ui/manual_is_ascii_check.rs:116:71 + --> tests/ui/manual_is_ascii_check.rs:118:71 | LL | let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| ('0'..='9').contains(c)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -199,5 +211,17 @@ LL - let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| ('0'. LL + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); | -error: aborting due to 29 previous errors +error: manual check for common ascii range + --> tests/ui/manual_is_ascii_check.rs:120:71 + | +LL | let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| matches!(c, '0'..='9')).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| matches!(c, '0'..='9')).collect(); +LL + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); + | + +error: aborting due to 31 previous errors diff --git a/src/tools/clippy/tests/ui/map_identity.fixed b/src/tools/clippy/tests/ui/map_identity.fixed index 6c971ba6338..a10aae8cc12 100644 --- a/src/tools/clippy/tests/ui/map_identity.fixed +++ b/src/tools/clippy/tests/ui/map_identity.fixed @@ -1,3 +1,4 @@ +//@require-annotations-for-level: ERROR #![warn(clippy::map_identity)] #![allow(clippy::needless_return, clippy::disallowed_names)] @@ -72,20 +73,33 @@ fn issue11764() { } fn issue13904() { - // don't lint: `it.next()` would not be legal as `it` is immutable - let it = [1, 2, 3].into_iter(); - let _ = it.map(|x| x).next(); + // lint, but there's a catch: + // when we remove the `.map()`, `it.next()` would require `it` to be mutable + // therefore, include that in the suggestion as well + let mut it = [1, 2, 3].into_iter(); + let _ = it.next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `it` mutable + + // lint + let mut index = [1, 2, 3].into_iter(); + let mut subindex = (index.by_ref().take(3), 42); + let _ = subindex.0.next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `subindex` mutable // lint #[allow(unused_mut)] let mut it = [1, 2, 3].into_iter(); let _ = it.next(); //~^ map_identity + //~| HELP: remove the call to `map` // lint let it = [1, 2, 3].into_iter(); let _ = { it }.next(); //~^ map_identity + //~| HELP: remove the call to `map` } // same as `issue11764`, but for arrays diff --git a/src/tools/clippy/tests/ui/map_identity.rs b/src/tools/clippy/tests/ui/map_identity.rs index 59dcfcda3b6..fc6e018f924 100644 --- a/src/tools/clippy/tests/ui/map_identity.rs +++ b/src/tools/clippy/tests/ui/map_identity.rs @@ -1,3 +1,4 @@ +//@require-annotations-for-level: ERROR #![warn(clippy::map_identity)] #![allow(clippy::needless_return, clippy::disallowed_names)] @@ -78,20 +79,33 @@ fn issue11764() { } fn issue13904() { - // don't lint: `it.next()` would not be legal as `it` is immutable + // lint, but there's a catch: + // when we remove the `.map()`, `it.next()` would require `it` to be mutable + // therefore, include that in the suggestion as well let it = [1, 2, 3].into_iter(); let _ = it.map(|x| x).next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `it` mutable + + // lint + let mut index = [1, 2, 3].into_iter(); + let subindex = (index.by_ref().take(3), 42); + let _ = subindex.0.map(|n| n).next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `subindex` mutable // lint #[allow(unused_mut)] let mut it = [1, 2, 3].into_iter(); let _ = it.map(|x| x).next(); //~^ map_identity + //~| HELP: remove the call to `map` // lint let it = [1, 2, 3].into_iter(); let _ = { it }.map(|x| x).next(); //~^ map_identity + //~| HELP: remove the call to `map` } // same as `issue11764`, but for arrays diff --git a/src/tools/clippy/tests/ui/map_identity.stderr b/src/tools/clippy/tests/ui/map_identity.stderr index a50c0d6b87b..8d19d62cdc6 100644 --- a/src/tools/clippy/tests/ui/map_identity.stderr +++ b/src/tools/clippy/tests/ui/map_identity.stderr @@ -1,5 +1,5 @@ error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:7:47 + --> tests/ui/map_identity.rs:8:47 | LL | let _: Vec<_> = x.iter().map(not_identity).map(|x| return x).collect(); | ^^^^^^^^^^^^^^^^^^ help: remove the call to `map` @@ -8,25 +8,25 @@ LL | let _: Vec<_> = x.iter().map(not_identity).map(|x| return x).collect(); = help: to override `-D warnings` add `#[allow(clippy::map_identity)]` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:9:57 + --> tests/ui/map_identity.rs:10:57 | LL | let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:9:29 + --> tests/ui/map_identity.rs:10:29 | LL | let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:12:32 + --> tests/ui/map_identity.rs:13:32 | LL | let _: Option<u8> = Some(3).map(|x| x); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:14:36 + --> tests/ui/map_identity.rs:15:36 | LL | let _: Result<i8, f32> = Ok(-3).map(|x| { | ____________________________________^ @@ -36,19 +36,19 @@ LL | | }); | |______^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:25:36 + --> tests/ui/map_identity.rs:26:36 | LL | let _: Result<u32, u32> = Ok(1).map_err(|a| a); | ^^^^^^^^^^^^^^^ help: remove the call to `map_err` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:36:22 + --> tests/ui/map_identity.rs:37:22 | LL | let _ = x.clone().map(|(x, y)| (x, y)); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:38:22 + --> tests/ui/map_identity.rs:39:22 | LL | let _ = x.clone().map(|(x, y)| { | ______________________^ @@ -58,76 +58,100 @@ LL | | }); | |______^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:42:22 + --> tests/ui/map_identity.rs:43:22 | LL | let _ = x.clone().map(|(x, y)| return (x, y)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:46:22 + --> tests/ui/map_identity.rs:47:22 | LL | let _ = y.clone().map(|(x, y, (z, (w,)))| (x, y, (z, (w,)))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:76:30 + --> tests/ui/map_identity.rs:77:30 | LL | let _ = x.iter().copied().map(|(x, y)| (x, y)); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:88:15 + --> tests/ui/map_identity.rs:86:15 + | +LL | let _ = it.map(|x| x).next(); + | ^^^^^^^^^^^ + | +help: remove the call to `map`, and make `it` mutable + | +LL ~ let mut it = [1, 2, 3].into_iter(); +LL ~ let _ = it.next(); + | + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:93:23 + | +LL | let _ = subindex.0.map(|n| n).next(); + | ^^^^^^^^^^^ + | +help: remove the call to `map`, and make `subindex` mutable + | +LL ~ let mut subindex = (index.by_ref().take(3), 42); +LL ~ let _ = subindex.0.next(); + | + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:100:15 | LL | let _ = it.map(|x| x).next(); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:93:19 + --> tests/ui/map_identity.rs:106:19 | LL | let _ = { it }.map(|x| x).next(); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:105:30 + --> tests/ui/map_identity.rs:119:30 | LL | let _ = x.iter().copied().map(|[x, y]| [x, y]); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:131:26 + --> tests/ui/map_identity.rs:145:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo, bar }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:135:26 + --> tests/ui/map_identity.rs:149:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| foo::Foo { foo, bar }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:143:26 + --> tests/ui/map_identity.rs:157:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: foo, bar: bar }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:147:26 + --> tests/ui/map_identity.rs:161:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { bar, foo }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:157:26 + --> tests/ui/map_identity.rs:171:26 | LL | let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(foo, bar)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:161:26 + --> tests/ui/map_identity.rs:175:26 | LL | let _ = x.into_iter().map(|Foo2(foo, bar)| foo::Foo2(foo, bar)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` -error: aborting due to 20 previous errors +error: aborting due to 22 previous errors diff --git a/src/tools/clippy/tests/ui/missing_inline.rs b/src/tools/clippy/tests/ui/missing_inline.rs index 223c7447975..8e937d60951 100644 --- a/src/tools/clippy/tests/ui/missing_inline.rs +++ b/src/tools/clippy/tests/ui/missing_inline.rs @@ -97,3 +97,10 @@ pub mod issue15301 { println!("Just called a Rust function from Rust!"); } } + +pub mod issue15491 { + pub trait Foo { + #[allow(clippy::missing_inline_in_public_items)] + fn foo(&self) {} + } +} diff --git a/src/tools/clippy/tests/ui/missing_transmute_annotations_unfixable.rs b/src/tools/clippy/tests/ui/missing_transmute_annotations_unfixable.rs new file mode 100644 index 00000000000..08ba3b791ee --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_transmute_annotations_unfixable.rs @@ -0,0 +1,29 @@ +//@no-rustfix + +fn issue14984() { + async fn e() {} + async fn x() -> u32 { + 0 + } + async fn y() -> f32 { + 0.0 + }; + let mut yy = unsafe { std::ptr::read(&y()) }; + yy = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + //~^ missing_transmute_annotations + + let mut zz = 0u8; + zz = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + //~^ missing_transmute_annotations + + yy = unsafe { std::mem::transmute(zz) }; + //~^ missing_transmute_annotations + + fn a() -> impl Sized { + 0u32 + } + + let mut b: f32 = 0.0; + b = unsafe { std::mem::transmute(a()) }; + //~^ missing_transmute_annotations +} diff --git a/src/tools/clippy/tests/ui/missing_transmute_annotations_unfixable.stderr b/src/tools/clippy/tests/ui/missing_transmute_annotations_unfixable.stderr new file mode 100644 index 00000000000..83efdce13f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_transmute_annotations_unfixable.stderr @@ -0,0 +1,36 @@ +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:12:29 + | +LL | yy = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + | ^^^^^^^^^ + | + = help: consider giving the source and destination types a name, and adding missing type annotations + = note: `-D clippy::missing-transmute-annotations` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_transmute_annotations)]` + +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:16:29 + | +LL | zz = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + | ^^^^^^^^^ + | + = help: consider giving the origin type a name, and adding missing type annotations + +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:19:29 + | +LL | yy = unsafe { std::mem::transmute(zz) }; + | ^^^^^^^^^ + | + = help: consider giving the destination type a name, and adding missing type annotations + +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:27:28 + | +LL | b = unsafe { std::mem::transmute(a()) }; + | ^^^^^^^^^ + | + = help: consider giving `a()`'s type a name, and adding missing type annotations + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_reference.fixed b/src/tools/clippy/tests/ui/mut_reference.fixed new file mode 100644 index 00000000000..03d854099e6 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_reference.fixed @@ -0,0 +1,170 @@ +#![allow(clippy::mut_mut)] + +fn takes_ref(a: &i32) {} +fn takes_refmut(a: &mut i32) {} +fn takes_ref_ref(a: &&i32) {} +fn takes_refmut_ref(a: &mut &i32) {} +fn takes_ref_refmut(a: &&mut i32) {} +fn takes_refmut_refmut(a: &mut &mut i32) {} +fn takes_raw_const(a: *const i32) {} +fn takes_raw_mut(a: *mut i32) {} + +mod issue11268 { + macro_rules! x { + (1 $f:expr) => { + $f(&mut 1); + }; + (2 $f:expr) => { + $f(&mut &1) + }; + (3 $f:expr) => { + $f(&mut &mut 1) + }; + (4 $f:expr) => { + let mut a = 1; + $f(&raw mut a) + }; + } + + fn f() { + x!(1 super::takes_ref); + x!(1 super::takes_refmut); + x!(2 super::takes_refmut_ref); + x!(3 super::takes_ref_refmut); + x!(3 super::takes_refmut_refmut); + x!(4 super::takes_raw_const); + x!(4 super::takes_raw_mut); + } +} + +struct MyStruct; + +impl MyStruct { + fn takes_ref(&self, a: &i32) {} + fn takes_refmut(&self, a: &mut i32) {} + fn takes_ref_ref(&self, a: &&i32) {} + fn takes_refmut_ref(&self, a: &mut &i32) {} + fn takes_ref_refmut(&self, a: &&mut i32) {} + fn takes_refmut_refmut(&self, a: &mut &mut i32) {} + fn takes_raw_const(&self, a: *const i32) {} + fn takes_raw_mut(&self, a: *mut i32) {} +} + +#[warn(clippy::unnecessary_mut_passed)] +fn main() { + // Functions + takes_ref(&42); + //~^ unnecessary_mut_passed + takes_ref_ref(&&42); + //~^ unnecessary_mut_passed + takes_ref_refmut(&&mut 42); + //~^ unnecessary_mut_passed + takes_raw_const(&42); + //~^ unnecessary_mut_passed + + let as_ptr: fn(&i32) = takes_ref; + as_ptr(&42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&&42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&&mut 42); + //~^ unnecessary_mut_passed + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&42); + //~^ unnecessary_mut_passed + + // Methods + let my_struct = MyStruct; + my_struct.takes_ref(&42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_ref(&&42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_refmut(&&mut 42); + //~^ unnecessary_mut_passed + my_struct.takes_raw_const(&42); + //~^ unnecessary_mut_passed + + // No error + + // Functions + takes_ref(&42); + let as_ptr: fn(&i32) = takes_ref; + as_ptr(&42); + + takes_refmut(&mut 42); + let as_ptr: fn(&mut i32) = takes_refmut; + as_ptr(&mut 42); + + takes_ref_ref(&&42); + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&&42); + + takes_refmut_ref(&mut &42); + let as_ptr: fn(&mut &i32) = takes_refmut_ref; + as_ptr(&mut &42); + + takes_ref_refmut(&&mut 42); + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&&mut 42); + + takes_refmut_refmut(&mut &mut 42); + let as_ptr: fn(&mut &mut i32) = takes_refmut_refmut; + as_ptr(&mut &mut 42); + + takes_raw_const(&42); + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&42); + + takes_raw_mut(&mut 42); + let as_ptr: fn(*mut i32) = takes_raw_mut; + as_ptr(&mut 42); + + let a = &mut 42; + let b = &mut &42; + let c = &mut &mut 42; + takes_ref(a); + takes_ref_ref(b); + takes_ref_refmut(c); + takes_raw_const(a); + + // Methods + my_struct.takes_ref(&42); + my_struct.takes_refmut(&mut 42); + my_struct.takes_ref_ref(&&42); + my_struct.takes_refmut_ref(&mut &42); + my_struct.takes_ref_refmut(&&mut 42); + my_struct.takes_refmut_refmut(&mut &mut 42); + my_struct.takes_raw_const(&42); + my_struct.takes_raw_mut(&mut 42); + my_struct.takes_ref(a); + my_struct.takes_ref_ref(b); + my_struct.takes_ref_refmut(c); + my_struct.takes_raw_const(a); + my_struct.takes_raw_mut(a); +} + +// not supported currently +fn raw_ptrs(my_struct: MyStruct) { + let mut n = 42; + + takes_raw_const(&raw mut n); + + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&raw mut n); + + my_struct.takes_raw_const(&raw mut n); + + // No error + + takes_raw_const(&raw const n); + takes_raw_mut(&raw mut n); + + let a = &raw mut n; + takes_raw_const(a); + + my_struct.takes_raw_const(&raw const n); + my_struct.takes_raw_mut(&raw mut n); + my_struct.takes_raw_const(a); +} diff --git a/src/tools/clippy/tests/ui/mut_reference.rs b/src/tools/clippy/tests/ui/mut_reference.rs index f664c373cdc..80e3f506927 100644 --- a/src/tools/clippy/tests/ui/mut_reference.rs +++ b/src/tools/clippy/tests/ui/mut_reference.rs @@ -1,60 +1,170 @@ -#![allow(unused_variables, dead_code)] -//@no-rustfix -fn takes_an_immutable_reference(a: &i32) {} -fn takes_a_mutable_reference(a: &mut i32) {} +#![allow(clippy::mut_mut)] + +fn takes_ref(a: &i32) {} +fn takes_refmut(a: &mut i32) {} +fn takes_ref_ref(a: &&i32) {} +fn takes_refmut_ref(a: &mut &i32) {} +fn takes_ref_refmut(a: &&mut i32) {} +fn takes_refmut_refmut(a: &mut &mut i32) {} +fn takes_raw_const(a: *const i32) {} +fn takes_raw_mut(a: *mut i32) {} mod issue11268 { macro_rules! x { - ($f:expr) => { + (1 $f:expr) => { $f(&mut 1); }; + (2 $f:expr) => { + $f(&mut &1) + }; + (3 $f:expr) => { + $f(&mut &mut 1) + }; + (4 $f:expr) => { + let mut a = 1; + $f(&raw mut a) + }; } fn f() { - x!(super::takes_an_immutable_reference); - x!(super::takes_a_mutable_reference); + x!(1 super::takes_ref); + x!(1 super::takes_refmut); + x!(2 super::takes_refmut_ref); + x!(3 super::takes_ref_refmut); + x!(3 super::takes_refmut_refmut); + x!(4 super::takes_raw_const); + x!(4 super::takes_raw_mut); } } struct MyStruct; impl MyStruct { - fn takes_an_immutable_reference(&self, a: &i32) {} - - fn takes_a_mutable_reference(&self, a: &mut i32) {} + fn takes_ref(&self, a: &i32) {} + fn takes_refmut(&self, a: &mut i32) {} + fn takes_ref_ref(&self, a: &&i32) {} + fn takes_refmut_ref(&self, a: &mut &i32) {} + fn takes_ref_refmut(&self, a: &&mut i32) {} + fn takes_refmut_refmut(&self, a: &mut &mut i32) {} + fn takes_raw_const(&self, a: *const i32) {} + fn takes_raw_mut(&self, a: *mut i32) {} } #[warn(clippy::unnecessary_mut_passed)] fn main() { // Functions - takes_an_immutable_reference(&mut 42); + takes_ref(&mut 42); + //~^ unnecessary_mut_passed + takes_ref_ref(&mut &42); + //~^ unnecessary_mut_passed + takes_ref_refmut(&mut &mut 42); + //~^ unnecessary_mut_passed + takes_raw_const(&mut 42); //~^ unnecessary_mut_passed - let as_ptr: fn(&i32) = takes_an_immutable_reference; + let as_ptr: fn(&i32) = takes_ref; + as_ptr(&mut 42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&mut &42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&mut &mut 42); + //~^ unnecessary_mut_passed + let as_ptr: fn(*const i32) = takes_raw_const; as_ptr(&mut 42); //~^ unnecessary_mut_passed // Methods let my_struct = MyStruct; - my_struct.takes_an_immutable_reference(&mut 42); + my_struct.takes_ref(&mut 42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_ref(&mut &42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_refmut(&mut &mut 42); + //~^ unnecessary_mut_passed + my_struct.takes_raw_const(&mut 42); //~^ unnecessary_mut_passed // No error // Functions - takes_an_immutable_reference(&42); - let as_ptr: fn(&i32) = takes_an_immutable_reference; + takes_ref(&42); + let as_ptr: fn(&i32) = takes_ref; as_ptr(&42); - takes_a_mutable_reference(&mut 42); - let as_ptr: fn(&mut i32) = takes_a_mutable_reference; + takes_refmut(&mut 42); + let as_ptr: fn(&mut i32) = takes_refmut; + as_ptr(&mut 42); + + takes_ref_ref(&&42); + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&&42); + + takes_refmut_ref(&mut &42); + let as_ptr: fn(&mut &i32) = takes_refmut_ref; + as_ptr(&mut &42); + + takes_ref_refmut(&&mut 42); + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&&mut 42); + + takes_refmut_refmut(&mut &mut 42); + let as_ptr: fn(&mut &mut i32) = takes_refmut_refmut; + as_ptr(&mut &mut 42); + + takes_raw_const(&42); + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&42); + + takes_raw_mut(&mut 42); + let as_ptr: fn(*mut i32) = takes_raw_mut; as_ptr(&mut 42); let a = &mut 42; - takes_an_immutable_reference(a); + let b = &mut &42; + let c = &mut &mut 42; + takes_ref(a); + takes_ref_ref(b); + takes_ref_refmut(c); + takes_raw_const(a); // Methods - my_struct.takes_an_immutable_reference(&42); - my_struct.takes_a_mutable_reference(&mut 42); - my_struct.takes_an_immutable_reference(a); + my_struct.takes_ref(&42); + my_struct.takes_refmut(&mut 42); + my_struct.takes_ref_ref(&&42); + my_struct.takes_refmut_ref(&mut &42); + my_struct.takes_ref_refmut(&&mut 42); + my_struct.takes_refmut_refmut(&mut &mut 42); + my_struct.takes_raw_const(&42); + my_struct.takes_raw_mut(&mut 42); + my_struct.takes_ref(a); + my_struct.takes_ref_ref(b); + my_struct.takes_ref_refmut(c); + my_struct.takes_raw_const(a); + my_struct.takes_raw_mut(a); +} + +// not supported currently +fn raw_ptrs(my_struct: MyStruct) { + let mut n = 42; + + takes_raw_const(&raw mut n); + + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&raw mut n); + + my_struct.takes_raw_const(&raw mut n); + + // No error + + takes_raw_const(&raw const n); + takes_raw_mut(&raw mut n); + + let a = &raw mut n; + takes_raw_const(a); + + my_struct.takes_raw_const(&raw const n); + my_struct.takes_raw_mut(&raw mut n); + my_struct.takes_raw_const(a); } diff --git a/src/tools/clippy/tests/ui/mut_reference.stderr b/src/tools/clippy/tests/ui/mut_reference.stderr index 474221329c2..5ecfaa37416 100644 --- a/src/tools/clippy/tests/ui/mut_reference.stderr +++ b/src/tools/clippy/tests/ui/mut_reference.stderr @@ -1,23 +1,77 @@ -error: the function `takes_an_immutable_reference` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:30:34 +error: the function `takes_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:56:15 | -LL | takes_an_immutable_reference(&mut 42); - | ^^^^^^^ +LL | takes_ref(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` | = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_mut_passed)]` +error: the function `takes_ref_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:58:19 + | +LL | takes_ref_ref(&mut &42); + | ^^^^^^^^ help: remove this `mut`: `&&42` + +error: the function `takes_ref_refmut` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:60:22 + | +LL | takes_ref_refmut(&mut &mut 42); + | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + +error: the function `takes_raw_const` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:62:21 + | +LL | takes_raw_const(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:66:12 + | +LL | as_ptr(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:69:12 + | +LL | as_ptr(&mut &42); + | ^^^^^^^^ help: remove this `mut`: `&&42` + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:72:12 + | +LL | as_ptr(&mut &mut 42); + | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:34:12 + --> tests/ui/mut_reference.rs:75:12 | LL | as_ptr(&mut 42); - | ^^^^^^^ + | ^^^^^^^ help: remove this `mut`: `&42` + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:80:25 + | +LL | my_struct.takes_ref(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + +error: the method `takes_ref_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:82:29 + | +LL | my_struct.takes_ref_ref(&mut &42); + | ^^^^^^^^ help: remove this `mut`: `&&42` + +error: the method `takes_ref_refmut` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:84:32 + | +LL | my_struct.takes_ref_refmut(&mut &mut 42); + | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` -error: the method `takes_an_immutable_reference` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:39:44 +error: the method `takes_raw_const` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:86:31 | -LL | my_struct.takes_an_immutable_reference(&mut 42); - | ^^^^^^^ +LL | my_struct.takes_raw_const(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` -error: aborting due to 3 previous errors +error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.stderr b/src/tools/clippy/tests/ui/needless_bool/fixable.stderr index 3f117ee5a50..9404d07ba0e 100644 --- a/src/tools/clippy/tests/ui/needless_bool/fixable.stderr +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.stderr @@ -135,7 +135,7 @@ error: equality checks against true are unnecessary --> tests/ui/needless_bool/fixable.rs:157:8 | LL | if x == true {}; - | ^^^^^^^^^ help: try simplifying it as shown: `x` + | ^^^^^^^^^ help: try: `x` | = note: `-D clippy::bool-comparison` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` @@ -144,19 +144,19 @@ error: equality checks against false can be replaced by a negation --> tests/ui/needless_bool/fixable.rs:162:8 | LL | if x == false {}; - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + | ^^^^^^^^^^ help: try: `!x` error: equality checks against true are unnecessary --> tests/ui/needless_bool/fixable.rs:173:8 | LL | if x == true {}; - | ^^^^^^^^^ help: try simplifying it as shown: `x` + | ^^^^^^^^^ help: try: `x` error: equality checks against false can be replaced by a negation --> tests/ui/needless_bool/fixable.rs:175:8 | LL | if x == false {}; - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + | ^^^^^^^^^^ help: try: `!x` error: this if-then-else expression returns a bool literal --> tests/ui/needless_bool/fixable.rs:185:12 diff --git a/src/tools/clippy/tests/ui/needless_for_each_unfixable.rs b/src/tools/clippy/tests/ui/needless_for_each_unfixable.rs index 159cbe49828..24ce5786b91 100644 --- a/src/tools/clippy/tests/ui/needless_for_each_unfixable.rs +++ b/src/tools/clippy/tests/ui/needless_for_each_unfixable.rs @@ -1,6 +1,6 @@ //@no-rustfix: overlapping suggestions #![warn(clippy::needless_for_each)] -#![allow(clippy::needless_return, clippy::uninlined_format_args)] +#![allow(clippy::needless_return)] fn main() { let v: Vec<i32> = Vec::new(); @@ -11,7 +11,22 @@ fn main() { if *v == 10 { return; } else { - println!("{}", v); + println!("{v}"); } }); } + +fn issue9912() { + let mut i = 0; + // Changing this to a `for` loop would break type inference + [].iter().for_each(move |_: &i32| { + //~^ needless_for_each + i += 1; + }); + + // Changing this would actually be okay, but we still suggest `MaybeIncorrect`ly + [1i32].iter().for_each(move |_: &i32| { + //~^ needless_for_each + i += 1; + }); +} diff --git a/src/tools/clippy/tests/ui/needless_for_each_unfixable.stderr b/src/tools/clippy/tests/ui/needless_for_each_unfixable.stderr index 3a3a240c5a3..6bc56db68a0 100644 --- a/src/tools/clippy/tests/ui/needless_for_each_unfixable.stderr +++ b/src/tools/clippy/tests/ui/needless_for_each_unfixable.stderr @@ -19,7 +19,7 @@ LL + LL + if *v == 10 { LL + return; LL + } else { -LL + println!("{}", v); +LL + println!("{v}"); LL + } LL + } | @@ -29,5 +29,39 @@ LL - return; LL + continue; | -error: aborting due to 1 previous error +error: needless use of `for_each` + --> tests/ui/needless_for_each_unfixable.rs:22:5 + | +LL | / [].iter().for_each(move |_: &i32| { +LL | | +LL | | i += 1; +LL | | }); + | |_______^ + | +help: try + | +LL ~ for _ in [].iter() { +LL + +LL + i += 1; +LL + } + | + +error: needless use of `for_each` + --> tests/ui/needless_for_each_unfixable.rs:28:5 + | +LL | / [1i32].iter().for_each(move |_: &i32| { +LL | | +LL | | i += 1; +LL | | }); + | |_______^ + | +help: try + | +LL ~ for _ in [1i32].iter() { +LL + +LL + i += 1; +LL + } + | + +error: aborting due to 3 previous errors diff --git a/src/tools/clippy/tests/ui/needless_range_loop.rs b/src/tools/clippy/tests/ui/needless_range_loop.rs index 70cf9fa7369..ea4591d8b71 100644 --- a/src/tools/clippy/tests/ui/needless_range_loop.rs +++ b/src/tools/clippy/tests/ui/needless_range_loop.rs @@ -210,3 +210,35 @@ fn needless_loop() { black_box([1, 2, 3, 4, 5, 6, 7, 8][i]); } } + +fn issue_15068() { + let a = vec![vec![0u8; MAX_LEN]; MAX_LEN]; + let b = vec![0u8; MAX_LEN]; + + for i in 0..MAX_LEN { + // no error + let _ = a[0][i]; + let _ = b[i]; + } + + for i in 0..MAX_LEN { + // no error + let _ = a[i][0]; + let _ = b[i]; + } + + for i in 0..MAX_LEN { + // no error + let _ = a[i][b[i] as usize]; + } + + for i in 0..MAX_LEN { + //~^ needless_range_loop + let _ = a[i][i]; + } + + for i in 0..MAX_LEN { + //~^ needless_range_loop + let _ = a[0][i]; + } +} diff --git a/src/tools/clippy/tests/ui/needless_range_loop.stderr b/src/tools/clippy/tests/ui/needless_range_loop.stderr index 23c085f9d3b..33a519d8a80 100644 --- a/src/tools/clippy/tests/ui/needless_range_loop.stderr +++ b/src/tools/clippy/tests/ui/needless_range_loop.stderr @@ -168,5 +168,29 @@ LL - for i in 0..vec.len() { LL + for (i, <item>) in vec.iter_mut().enumerate() { | -error: aborting due to 14 previous errors +error: the loop variable `i` is used to index `a` + --> tests/ui/needless_range_loop.rs:235:14 + | +LL | for i in 0..MAX_LEN { + | ^^^^^^^^^^ + | +help: consider using an iterator and enumerate() + | +LL - for i in 0..MAX_LEN { +LL + for (i, <item>) in a.iter().enumerate().take(MAX_LEN) { + | + +error: the loop variable `i` is only used to index `a` + --> tests/ui/needless_range_loop.rs:240:14 + | +LL | for i in 0..MAX_LEN { + | ^^^^^^^^^^ + | +help: consider using an iterator + | +LL - for i in 0..MAX_LEN { +LL + for <item> in a.iter().take(MAX_LEN) { + | + +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.rs b/src/tools/clippy/tests/ui/nonminimal_bool.rs index cacce9a7d1c..f03f74dfafe 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool.rs +++ b/src/tools/clippy/tests/ui/nonminimal_bool.rs @@ -182,14 +182,12 @@ fn issue_5794() { if !b == true {} //~^ nonminimal_bool //~| bool_comparison - //~| bool_comparison if !b != true {} //~^ nonminimal_bool //~| bool_comparison if true == !b {} //~^ nonminimal_bool //~| bool_comparison - //~| bool_comparison if true != !b {} //~^ nonminimal_bool //~| bool_comparison diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.stderr b/src/tools/clippy/tests/ui/nonminimal_bool.stderr index c20412974b2..6a20b9216da 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool.stderr +++ b/src/tools/clippy/tests/ui/nonminimal_bool.stderr @@ -154,98 +154,86 @@ error: this boolean expression can be simplified LL | if !b == true {} | ^^^^^^^^^^ help: try: `b != true` -error: this comparison might be written more concisely +error: equality checks against true are unnecessary --> tests/ui/nonminimal_bool.rs:182:8 | LL | if !b == true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `b != true` + | ^^^^^^^^^^ help: try: `!b` | = note: `-D clippy::bool-comparison` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` -error: equality checks against true are unnecessary - --> tests/ui/nonminimal_bool.rs:182:8 - | -LL | if !b == true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!b` - error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:186:8 + --> tests/ui/nonminimal_bool.rs:185:8 | LL | if !b != true {} | ^^^^^^^^^^ help: try: `b == true` error: inequality checks against true can be replaced by a negation - --> tests/ui/nonminimal_bool.rs:186:8 + --> tests/ui/nonminimal_bool.rs:185:8 | LL | if !b != true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `b` + | ^^^^^^^^^^ help: try: `b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:189:8 + --> tests/ui/nonminimal_bool.rs:188:8 | LL | if true == !b {} | ^^^^^^^^^^ help: try: `true != b` -error: this comparison might be written more concisely - --> tests/ui/nonminimal_bool.rs:189:8 - | -LL | if true == !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `true != b` - error: equality checks against true are unnecessary - --> tests/ui/nonminimal_bool.rs:189:8 + --> tests/ui/nonminimal_bool.rs:188:8 | LL | if true == !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!b` + | ^^^^^^^^^^ help: try: `!b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:193:8 + --> tests/ui/nonminimal_bool.rs:191:8 | LL | if true != !b {} | ^^^^^^^^^^ help: try: `true == b` error: inequality checks against true can be replaced by a negation - --> tests/ui/nonminimal_bool.rs:193:8 + --> tests/ui/nonminimal_bool.rs:191:8 | LL | if true != !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `b` + | ^^^^^^^^^^ help: try: `b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:196:8 + --> tests/ui/nonminimal_bool.rs:194:8 | LL | if !b == !c {} | ^^^^^^^^ help: try: `b == c` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:198:8 + --> tests/ui/nonminimal_bool.rs:196:8 | LL | if !b != !c {} | ^^^^^^^^ help: try: `b != c` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:214:8 + --> tests/ui/nonminimal_bool.rs:212:8 | LL | if !(a < 2.0 && !b) { | ^^^^^^^^^^^^^^^^ help: try: `a >= 2.0 || b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:233:12 + --> tests/ui/nonminimal_bool.rs:231:12 | LL | if !(matches!(ty, TyKind::Ref(_, _, _)) && !is_mutable(&expr)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!matches!(ty, TyKind::Ref(_, _, _)) || is_mutable(&expr)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:253:8 + --> tests/ui/nonminimal_bool.rs:251:8 | LL | if !S != true {} | ^^^^^^^^^^ help: try: `S == true` error: inequality checks against true can be replaced by a negation - --> tests/ui/nonminimal_bool.rs:253:8 + --> tests/ui/nonminimal_bool.rs:251:8 | LL | if !S != true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!!S` + | ^^^^^^^^^^ help: try: `!!S` -error: aborting due to 33 previous errors +error: aborting due to 31 previous errors diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed index 55c1b8f110c..340be7c7e93 100644 --- a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed @@ -102,4 +102,13 @@ fn option_map_unit_fn() { //~^ option_map_unit_fn } +fn issue15568() { + unsafe fn f(_: u32) {} + let x = Some(3); + if let Some(x) = x { unsafe { f(x) } } + //~^ option_map_unit_fn + if let Some(x) = x { unsafe { f(x) } } + //~^ option_map_unit_fn +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs index 5ed47e4c60b..d902c87379b 100644 --- a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs @@ -102,4 +102,13 @@ fn option_map_unit_fn() { //~^ option_map_unit_fn } +fn issue15568() { + unsafe fn f(_: u32) {} + let x = Some(3); + x.map(|x| unsafe { f(x) }); + //~^ option_map_unit_fn + x.map(|x| unsafe { { f(x) } }); + //~^ option_map_unit_fn +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr index 3f7abae34ee..2405aa9a7cc 100644 --- a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr @@ -153,5 +153,21 @@ LL | option().map(|value| println!("{:?}", value)); | | | help: try: `if let Some(value) = option() { println!("{:?}", value) }` -error: aborting due to 19 previous errors +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_fixable.rs:108:5 + | +LL | x.map(|x| unsafe { f(x) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try: `if let Some(x) = x { unsafe { f(x) } }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_fixable.rs:110:5 + | +LL | x.map(|x| unsafe { { f(x) } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try: `if let Some(x) = x { unsafe { f(x) } }` + +error: aborting due to 21 previous errors diff --git a/src/tools/clippy/tests/ui/or_then_unwrap.fixed b/src/tools/clippy/tests/ui/or_then_unwrap.fixed index ba9beef57af..9660b82fe7d 100644 --- a/src/tools/clippy/tests/ui/or_then_unwrap.fixed +++ b/src/tools/clippy/tests/ui/or_then_unwrap.fixed @@ -28,6 +28,12 @@ fn main() { // //~^^ or_then_unwrap + // Call with macro should preserve the macro call rather than expand it + let option: Option<Vec<&str>> = None; + let _ = option.unwrap_or(vec!["fallback"]); // should trigger lint + // + //~^^ or_then_unwrap + // as part of a method chain let option: Option<&str> = None; let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint diff --git a/src/tools/clippy/tests/ui/or_then_unwrap.rs b/src/tools/clippy/tests/ui/or_then_unwrap.rs index fac90249a24..c3873352116 100644 --- a/src/tools/clippy/tests/ui/or_then_unwrap.rs +++ b/src/tools/clippy/tests/ui/or_then_unwrap.rs @@ -28,6 +28,12 @@ fn main() { // //~^^ or_then_unwrap + // Call with macro should preserve the macro call rather than expand it + let option: Option<Vec<&str>> = None; + let _ = option.or(Some(vec!["fallback"])).unwrap(); // should trigger lint + // + //~^^ or_then_unwrap + // as part of a method chain let option: Option<&str> = None; let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint diff --git a/src/tools/clippy/tests/ui/or_then_unwrap.stderr b/src/tools/clippy/tests/ui/or_then_unwrap.stderr index 1160498c605..3e66b15edbd 100644 --- a/src/tools/clippy/tests/ui/or_then_unwrap.stderr +++ b/src/tools/clippy/tests/ui/or_then_unwrap.stderr @@ -14,10 +14,16 @@ LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger l | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or("fallback")` error: found `.or(Some(…)).unwrap()` - --> tests/ui/or_then_unwrap.rs:33:31 + --> tests/ui/or_then_unwrap.rs:33:20 + | +LL | let _ = option.or(Some(vec!["fallback"])).unwrap(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or(vec!["fallback"])` + +error: found `.or(Some(…)).unwrap()` + --> tests/ui/or_then_unwrap.rs:39:31 | LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or("fallback")` -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/panicking_macros.rs b/src/tools/clippy/tests/ui/panicking_macros.rs index 65854d7eb4b..b044be7d54a 100644 --- a/src/tools/clippy/tests/ui/panicking_macros.rs +++ b/src/tools/clippy/tests/ui/panicking_macros.rs @@ -31,6 +31,20 @@ fn panic() { let b = a + 2; } +const fn panic_const() { + let a = 2; + panic!(); + //~^ panic + + panic!("message"); + //~^ panic + + panic!("{} {}", "panic with", "multiple arguments"); + //~^ panic + + let b = a + 2; +} + fn todo() { let a = 2; todo!(); @@ -114,6 +128,7 @@ fn debug_assert_msg() { fn main() { panic(); + panic_const(); todo(); unimplemented(); unreachable(); diff --git a/src/tools/clippy/tests/ui/panicking_macros.stderr b/src/tools/clippy/tests/ui/panicking_macros.stderr index 03e459e4ec6..7b767ae0f65 100644 --- a/src/tools/clippy/tests/ui/panicking_macros.stderr +++ b/src/tools/clippy/tests/ui/panicking_macros.stderr @@ -19,9 +19,27 @@ error: `panic` should not be present in production code LL | panic!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `todo` should not be present in production code +error: `panic` should not be present in production code --> tests/ui/panicking_macros.rs:36:5 | +LL | panic!(); + | ^^^^^^^^ + +error: `panic` should not be present in production code + --> tests/ui/panicking_macros.rs:39:5 + | +LL | panic!("message"); + | ^^^^^^^^^^^^^^^^^ + +error: `panic` should not be present in production code + --> tests/ui/panicking_macros.rs:42:5 + | +LL | panic!("{} {}", "panic with", "multiple arguments"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `todo` should not be present in production code + --> tests/ui/panicking_macros.rs:50:5 + | LL | todo!(); | ^^^^^^^ | @@ -29,19 +47,19 @@ LL | todo!(); = help: to override `-D warnings` add `#[allow(clippy::todo)]` error: `todo` should not be present in production code - --> tests/ui/panicking_macros.rs:39:5 + --> tests/ui/panicking_macros.rs:53:5 | LL | todo!("message"); | ^^^^^^^^^^^^^^^^ error: `todo` should not be present in production code - --> tests/ui/panicking_macros.rs:42:5 + --> tests/ui/panicking_macros.rs:56:5 | LL | todo!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:50:5 + --> tests/ui/panicking_macros.rs:64:5 | LL | unimplemented!(); | ^^^^^^^^^^^^^^^^ @@ -50,19 +68,19 @@ LL | unimplemented!(); = help: to override `-D warnings` add `#[allow(clippy::unimplemented)]` error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:53:5 + --> tests/ui/panicking_macros.rs:67:5 | LL | unimplemented!("message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:56:5 + --> tests/ui/panicking_macros.rs:70:5 | LL | unimplemented!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:64:5 + --> tests/ui/panicking_macros.rs:78:5 | LL | unreachable!(); | ^^^^^^^^^^^^^^ @@ -71,40 +89,40 @@ LL | unreachable!(); = help: to override `-D warnings` add `#[allow(clippy::unreachable)]` error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:67:5 + --> tests/ui/panicking_macros.rs:81:5 | LL | unreachable!("message"); | ^^^^^^^^^^^^^^^^^^^^^^^ error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:70:5 + --> tests/ui/panicking_macros.rs:84:5 | LL | unreachable!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `panic` should not be present in production code - --> tests/ui/panicking_macros.rs:78:5 + --> tests/ui/panicking_macros.rs:92:5 | LL | panic!(); | ^^^^^^^^ error: `todo` should not be present in production code - --> tests/ui/panicking_macros.rs:81:5 + --> tests/ui/panicking_macros.rs:95:5 | LL | todo!(); | ^^^^^^^ error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:84:5 + --> tests/ui/panicking_macros.rs:98:5 | LL | unimplemented!(); | ^^^^^^^^^^^^^^^^ error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:87:5 + --> tests/ui/panicking_macros.rs:101:5 | LL | unreachable!(); | ^^^^^^^^^^^^^^ -error: aborting due to 16 previous errors +error: aborting due to 19 previous errors diff --git a/src/tools/clippy/tests/ui/print_literal.fixed b/src/tools/clippy/tests/ui/print_literal.fixed index ebfe19c700e..26139f9b671 100644 --- a/src/tools/clippy/tests/ui/print_literal.fixed +++ b/src/tools/clippy/tests/ui/print_literal.fixed @@ -105,3 +105,16 @@ fn issue_14930() { println!("Hello x is {0:2$.1$}", 0.01, 2, 3); //~^ print_literal } + +fn issue_15576() { + println!("Hello x is {1:.*}", 5, 0.01); + //~^ print_literal + + println!("Hello x is {:.p$}", 0.01, p = 5); + //~^ print_literal + + println!( + "Hello name: x is {1:.*} (which {1} with {0} places)", 5, 0.01 + ); + //~^^ print_literal +} diff --git a/src/tools/clippy/tests/ui/print_literal.rs b/src/tools/clippy/tests/ui/print_literal.rs index 8f3d9be0698..7c4cf028e84 100644 --- a/src/tools/clippy/tests/ui/print_literal.rs +++ b/src/tools/clippy/tests/ui/print_literal.rs @@ -106,3 +106,17 @@ fn issue_14930() { println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); //~^ print_literal } + +fn issue_15576() { + println!("Hello {} is {2:.*}", "x", 5, 0.01); + //~^ print_literal + + println!("Hello {} is {:.p$}", "x", 0.01, p = 5); + //~^ print_literal + + println!( + "Hello {}: {2} is {3:.*} (which {3} with {1} places)", + "name", 5, "x", 0.01 + ); + //~^^ print_literal +} diff --git a/src/tools/clippy/tests/ui/print_literal.stderr b/src/tools/clippy/tests/ui/print_literal.stderr index 1c378017d15..c136f52800f 100644 --- a/src/tools/clippy/tests/ui/print_literal.stderr +++ b/src/tools/clippy/tests/ui/print_literal.stderr @@ -277,5 +277,41 @@ LL - println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); | -error: aborting due to 22 previous errors +error: literal with an empty format string + --> tests/ui/print_literal.rs:111:36 + | +LL | println!("Hello {} is {2:.*}", "x", 5, 0.01); + | ^^^ + | +help: try + | +LL - println!("Hello {} is {2:.*}", "x", 5, 0.01); +LL + println!("Hello x is {1:.*}", 5, 0.01); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:114:36 + | +LL | println!("Hello {} is {:.p$}", "x", 0.01, p = 5); + | ^^^ + | +help: try + | +LL - println!("Hello {} is {:.p$}", "x", 0.01, p = 5); +LL + println!("Hello x is {:.p$}", 0.01, p = 5); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:119:9 + | +LL | "name", 5, "x", 0.01 + | ^^^^^^^^^^^^^^ + | +help: try + | +LL - "Hello {}: {2} is {3:.*} (which {3} with {1} places)", +LL + "Hello name: x is {1:.*} (which {1} with {0} places)", 5, 0.01 + | + +error: aborting due to 25 previous errors diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed index 78e1ceb480a..4457878b6fa 100644 --- a/src/tools/clippy/tests/ui/ptr_as_ptr.fixed +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.fixed @@ -229,3 +229,9 @@ fn issue15283() { //~^ ptr_as_ptr } } + +fn issue15232() { + let mut b = Box::new(0i32); + let _ptr = std::ptr::addr_of_mut!(*b).cast::<()>(); + //~^ ptr_as_ptr +} diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.rs b/src/tools/clippy/tests/ui/ptr_as_ptr.rs index 70732cf0a6c..9124fc912d2 100644 --- a/src/tools/clippy/tests/ui/ptr_as_ptr.rs +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.rs @@ -229,3 +229,9 @@ fn issue15283() { //~^ ptr_as_ptr } } + +fn issue15232() { + let mut b = Box::new(0i32); + let _ptr = std::ptr::addr_of_mut!(*b) as *mut (); + //~^ ptr_as_ptr +} diff --git a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr index c0a2a4b6d20..af21c1e35f5 100644 --- a/src/tools/clippy/tests/ui/ptr_as_ptr.stderr +++ b/src/tools/clippy/tests/ui/ptr_as_ptr.stderr @@ -199,5 +199,11 @@ error: `as` casting between raw pointers without changing their constness LL | let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::<u8>()` -error: aborting due to 33 previous errors +error: `as` casting between raw pointers without changing their constness + --> tests/ui/ptr_as_ptr.rs:235:16 + | +LL | let _ptr = std::ptr::addr_of_mut!(*b) as *mut (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `std::ptr::addr_of_mut!(*b).cast::<()>()` + +error: aborting due to 34 previous errors diff --git a/src/tools/clippy/tests/ui/ptr_cast_constness.fixed b/src/tools/clippy/tests/ui/ptr_cast_constness.fixed index 79bfae1f7eb..cf57de53d9f 100644 --- a/src/tools/clippy/tests/ui/ptr_cast_constness.fixed +++ b/src/tools/clippy/tests/ui/ptr_cast_constness.fixed @@ -106,3 +106,9 @@ fn issue14621() { let _ = std::ptr::addr_of_mut!(local).cast_const(); //~^ ptr_cast_constness } + +fn issue11317() { + let r = &0_u32; + let _ptr: *mut u32 = (r as *const u32).cast_mut(); + //~^ ptr_cast_constness +} diff --git a/src/tools/clippy/tests/ui/ptr_cast_constness.rs b/src/tools/clippy/tests/ui/ptr_cast_constness.rs index f6590dabd5b..ea53a0fa8c5 100644 --- a/src/tools/clippy/tests/ui/ptr_cast_constness.rs +++ b/src/tools/clippy/tests/ui/ptr_cast_constness.rs @@ -106,3 +106,9 @@ fn issue14621() { let _ = std::ptr::addr_of_mut!(local) as *const _; //~^ ptr_cast_constness } + +fn issue11317() { + let r = &0_u32; + let _ptr: *mut u32 = r as *const _ as *mut _; + //~^ ptr_cast_constness +} diff --git a/src/tools/clippy/tests/ui/ptr_cast_constness.stderr b/src/tools/clippy/tests/ui/ptr_cast_constness.stderr index 0b1644168ff..4adb5cc5ad7 100644 --- a/src/tools/clippy/tests/ui/ptr_cast_constness.stderr +++ b/src/tools/clippy/tests/ui/ptr_cast_constness.stderr @@ -89,5 +89,11 @@ error: `as` casting between raw pointers while changing only its constness LL | let _ = std::ptr::addr_of_mut!(local) as *const _; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_const`, a safer alternative: `std::ptr::addr_of_mut!(local).cast_const()` -error: aborting due to 14 previous errors +error: `as` casting between raw pointers while changing only its constness + --> tests/ui/ptr_cast_constness.rs:112:26 + | +LL | let _ptr: *mut u32 = r as *const _ as *mut _; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `(r as *const u32).cast_mut()` + +error: aborting due to 15 previous errors diff --git a/src/tools/clippy/tests/ui/semicolon_inside_block.fixed b/src/tools/clippy/tests/ui/semicolon_inside_block.fixed index 7eb53e733ad..7308e78aae2 100644 --- a/src/tools/clippy/tests/ui/semicolon_inside_block.fixed +++ b/src/tools/clippy/tests/ui/semicolon_inside_block.fixed @@ -86,3 +86,8 @@ fn main() { unit_fn_block() } + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0}; +} diff --git a/src/tools/clippy/tests/ui/semicolon_inside_block.rs b/src/tools/clippy/tests/ui/semicolon_inside_block.rs index 9fa5b117194..467bf4d779f 100644 --- a/src/tools/clippy/tests/ui/semicolon_inside_block.rs +++ b/src/tools/clippy/tests/ui/semicolon_inside_block.rs @@ -86,3 +86,8 @@ fn main() { unit_fn_block() } + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0}; +} diff --git a/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed b/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed new file mode 100644 index 00000000000..5b93a91da00 --- /dev/null +++ b/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed @@ -0,0 +1,11 @@ +// Test when the feature `stmt_expr_attributes` is enabled + +#![feature(stmt_expr_attributes)] +#![allow(clippy::no_effect)] +#![warn(clippy::semicolon_inside_block)] + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0;} + //~^ semicolon_inside_block +} diff --git a/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.rs b/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.rs new file mode 100644 index 00000000000..aa2c0cb5029 --- /dev/null +++ b/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.rs @@ -0,0 +1,11 @@ +// Test when the feature `stmt_expr_attributes` is enabled + +#![feature(stmt_expr_attributes)] +#![allow(clippy::no_effect)] +#![warn(clippy::semicolon_inside_block)] + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0}; + //~^ semicolon_inside_block +} diff --git a/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr b/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr new file mode 100644 index 00000000000..5bb91915a5f --- /dev/null +++ b/src/tools/clippy/tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr @@ -0,0 +1,16 @@ +error: consider moving the `;` inside the block for consistent formatting + --> tests/ui/semicolon_inside_block_stmt_expr_attrs.rs:9:5 + | +LL | {0; 0}; + | ^^^^^^^ + | + = note: `-D clippy::semicolon-inside-block` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::semicolon_inside_block)]` +help: put the `;` here + | +LL - {0; 0}; +LL + {0; 0;} + | + +error: aborting due to 1 previous error + diff --git a/src/tools/clippy/tests/ui/std_instead_of_core.fixed b/src/tools/clippy/tests/ui/std_instead_of_core.fixed index 603ab0accb0..c27cec55824 100644 --- a/src/tools/clippy/tests/ui/std_instead_of_core.fixed +++ b/src/tools/clippy/tests/ui/std_instead_of_core.fixed @@ -89,3 +89,10 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: core::net::IpAddr) {} //~^ std_instead_of_core + +#[warn(clippy::alloc_instead_of_core)] +fn issue15579() { + use std::alloc; + + let layout = alloc::Layout::new::<u8>(); +} diff --git a/src/tools/clippy/tests/ui/std_instead_of_core.rs b/src/tools/clippy/tests/ui/std_instead_of_core.rs index b6d4abad9f8..7d53f7fc307 100644 --- a/src/tools/clippy/tests/ui/std_instead_of_core.rs +++ b/src/tools/clippy/tests/ui/std_instead_of_core.rs @@ -89,3 +89,10 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: std::net::IpAddr) {} //~^ std_instead_of_core + +#[warn(clippy::alloc_instead_of_core)] +fn issue15579() { + use std::alloc; + + let layout = alloc::Layout::new::<u8>(); +} diff --git a/src/tools/clippy/tests/ui/unit_cmp.rs b/src/tools/clippy/tests/ui/unit_cmp.rs index 93f5b87c3d2..0a0fe3a1990 100644 --- a/src/tools/clippy/tests/ui/unit_cmp.rs +++ b/src/tools/clippy/tests/ui/unit_cmp.rs @@ -68,3 +68,20 @@ fn main() { } ); } + +fn issue15559() { + fn foo() {} + assert_eq!( + //~^ unit_cmp + { + 1; + }, + foo() + ); + assert_eq!(foo(), foo()); + //~^ unit_cmp + + // don't lint on explicitly written unit expr + assert_eq!(foo(), ()); + assert_ne!((), ContainsUnit(()).0); +} diff --git a/src/tools/clippy/tests/ui/unit_cmp.stderr b/src/tools/clippy/tests/ui/unit_cmp.stderr index 8f26749fd86..21aa9dce151 100644 --- a/src/tools/clippy/tests/ui/unit_cmp.stderr +++ b/src/tools/clippy/tests/ui/unit_cmp.stderr @@ -71,5 +71,23 @@ LL | | true; LL | | ); | |_____^ -error: aborting due to 6 previous errors +error: `assert_eq` of unit values detected. This will always succeed + --> tests/ui/unit_cmp.rs:74:5 + | +LL | / assert_eq!( +LL | | +LL | | { +LL | | 1; +LL | | }, +LL | | foo() +LL | | ); + | |_____^ + +error: `assert_eq` of unit values detected. This will always succeed + --> tests/ui/unit_cmp.rs:81:5 + | +LL | assert_eq!(foo(), foo()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_clone.stderr b/src/tools/clippy/tests/ui/unnecessary_clone.stderr index a4fdf09cd5d..17518e123d1 100644 --- a/src/tools/clippy/tests/ui/unnecessary_clone.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_clone.stderr @@ -2,7 +2,7 @@ error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:23:5 | LL | rc.clone(); - | ^^^^^^^^^^ help: try: `Rc::<bool>::clone(&rc)` + | ^^^^^^^^^^ help: try: `std::rc::Rc::<bool>::clone(&rc)` | = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::clone_on_ref_ptr)]` @@ -11,25 +11,25 @@ error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:28:5 | LL | arc.clone(); - | ^^^^^^^^^^^ help: try: `Arc::<bool>::clone(&arc)` + | ^^^^^^^^^^^ help: try: `std::sync::Arc::<bool>::clone(&arc)` error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:33:5 | LL | rcweak.clone(); - | ^^^^^^^^^^^^^^ help: try: `Weak::<bool>::clone(&rcweak)` + | ^^^^^^^^^^^^^^ help: try: `std::rc::Weak::<bool>::clone(&rcweak)` error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:38:5 | LL | arc_weak.clone(); - | ^^^^^^^^^^^^^^^^ help: try: `Weak::<bool>::clone(&arc_weak)` + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::Weak::<bool>::clone(&arc_weak)` error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:44:33 | LL | let _: Arc<dyn SomeTrait> = x.clone(); - | ^^^^^^^^^ help: try: `Arc::<SomeImpl>::clone(&x)` + | ^^^^^^^^^ help: try: `std::sync::Arc::<SomeImpl>::clone(&x)` error: using `clone` on type `T` which implements the `Copy` trait --> tests/ui/unnecessary_clone.rs:49:5 @@ -56,7 +56,7 @@ error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:108:14 | LL | Some(try_opt!(Some(rc)).clone()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Rc::<u8>::clone(&try_opt!(Some(rc)))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::rc::Rc::<u8>::clone(&try_opt!(Some(rc)))` error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns.fixed b/src/tools/clippy/tests/ui/unnested_or_patterns.fixed index 2081772d06b..339d4a95084 100644 --- a/src/tools/clippy/tests/ui/unnested_or_patterns.fixed +++ b/src/tools/clippy/tests/ui/unnested_or_patterns.fixed @@ -9,6 +9,11 @@ )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] +struct S { + x: u8, + y: u8, +} + fn main() { // Should be ignored by this lint, as nesting requires more characters. if let &0 | &2 = &0 {} @@ -45,10 +50,6 @@ fn main() { //~^ unnested_or_patterns if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {} //~^ unnested_or_patterns - struct S { - x: u8, - y: u8, - } if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {} //~^ unnested_or_patterns if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} @@ -77,3 +78,19 @@ mod issue9952 { fn or_in_param((x | x | x): i32) {} //~^ unnested_or_patterns } + +fn issue15219() { + struct Foo { + x: u8, + } + + // the original repro + if let Foo { x } | Foo { x } = (Foo { x: 0 }) {} + + // also works with more fields + if let S { x, y } | S { x, y } = (S { x: 0, y: 0 }) {} + + // `y` not triggering the lint doesn't stop the `x` from getting flagged + if let S { y, x: 0 | 1 } = (S { x: 0, y: 1 }) {} + //~^ unnested_or_patterns +} diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns.rs b/src/tools/clippy/tests/ui/unnested_or_patterns.rs index 6bf8fce3661..f5c99183b0c 100644 --- a/src/tools/clippy/tests/ui/unnested_or_patterns.rs +++ b/src/tools/clippy/tests/ui/unnested_or_patterns.rs @@ -9,6 +9,11 @@ )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] +struct S { + x: u8, + y: u8, +} + fn main() { // Should be ignored by this lint, as nesting requires more characters. if let &0 | &2 = &0 {} @@ -45,10 +50,6 @@ fn main() { //~^ unnested_or_patterns if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {} //~^ unnested_or_patterns - struct S { - x: u8, - y: u8, - } if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} //~^ unnested_or_patterns if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} @@ -77,3 +78,19 @@ mod issue9952 { fn or_in_param((x | (x | x)): i32) {} //~^ unnested_or_patterns } + +fn issue15219() { + struct Foo { + x: u8, + } + + // the original repro + if let Foo { x } | Foo { x } = (Foo { x: 0 }) {} + + // also works with more fields + if let S { x, y } | S { x, y } = (S { x: 0, y: 0 }) {} + + // `y` not triggering the lint doesn't stop the `x` from getting flagged + if let S { y, x: 0 } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} + //~^ unnested_or_patterns +} diff --git a/src/tools/clippy/tests/ui/unnested_or_patterns.stderr b/src/tools/clippy/tests/ui/unnested_or_patterns.stderr index c805dc992b1..d2b617c322c 100644 --- a/src/tools/clippy/tests/ui/unnested_or_patterns.stderr +++ b/src/tools/clippy/tests/ui/unnested_or_patterns.stderr @@ -1,5 +1,5 @@ error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:16:12 + --> tests/ui/unnested_or_patterns.rs:21:12 | LL | if let box 0 | box 2 = Box::new(0) {} | ^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + if let box (0 | 2) = Box::new(0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:18:12 + --> tests/ui/unnested_or_patterns.rs:23:12 | LL | if let box ((0 | 1)) | box (2 | 3) | box 4 = Box::new(0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:21:12 + --> tests/ui/unnested_or_patterns.rs:26:12 | LL | if let Some(1) | C0 | Some(2) = None {} | ^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + if let Some(1 | 2) | C0 = None {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:23:12 + --> tests/ui/unnested_or_patterns.rs:28:12 | LL | if let &mut 0 | &mut 2 = &mut 0 {} | ^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + if let &mut (0 | 2) = &mut 0 {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:25:12 + --> tests/ui/unnested_or_patterns.rs:30:12 | LL | if let x @ 0 | x @ 2 = 0 {} | ^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + if let x @ (0 | 2) = 0 {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:27:12 + --> tests/ui/unnested_or_patterns.rs:32:12 | LL | if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + if let (0, 1 | 2 | 3) = (0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:29:12 + --> tests/ui/unnested_or_patterns.rs:34:12 | LL | if let (1, 0) | (2, 0) | (3, 0) = (0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + if let (1 | 2 | 3, 0) = (0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:31:12 + --> tests/ui/unnested_or_patterns.rs:36:12 | LL | if let (x, ..) | (x, 1) | (x, 2) = (0, 1) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL + if let (x, ..) | (x, 1 | 2) = (0, 1) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:33:12 + --> tests/ui/unnested_or_patterns.rs:38:12 | LL | if let [0] | [1] = [0] {} | ^^^^^^^^^ @@ -109,7 +109,7 @@ LL + if let [0 | 1] = [0] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:35:12 + --> tests/ui/unnested_or_patterns.rs:40:12 | LL | if let [x, 0] | [x, 1] = [0, 1] {} | ^^^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL + if let [x, 0 | 1] = [0, 1] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:37:12 + --> tests/ui/unnested_or_patterns.rs:42:12 | LL | if let [x, 0] | [x, 1] | [x, 2] = [0, 1] {} | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL + if let [x, 0 | 1 | 2] = [0, 1] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:39:12 + --> tests/ui/unnested_or_patterns.rs:44:12 | LL | if let [x, ..] | [x, 1] | [x, 2] = [0, 1] {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL + if let [x, ..] | [x, 1 | 2] = [0, 1] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:42:12 + --> tests/ui/unnested_or_patterns.rs:47:12 | LL | if let TS(0, x) | TS(1, x) = TS(0, 0) {} | ^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + if let TS(0 | 1, x) = TS(0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:44:12 + --> tests/ui/unnested_or_patterns.rs:49:12 | LL | if let TS(1, 0) | TS(2, 0) | TS(3, 0) = TS(0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + if let TS(1 | 2 | 3, 0) = TS(0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:46:12 + --> tests/ui/unnested_or_patterns.rs:51:12 | LL | if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:52:12 + --> tests/ui/unnested_or_patterns.rs:53:12 | LL | if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -193,7 +193,7 @@ LL + if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:64:12 + --> tests/ui/unnested_or_patterns.rs:65:12 | LL | if let [1] | [53] = [0] {} | ^^^^^^^^^^ @@ -205,7 +205,7 @@ LL + if let [1 | 53] = [0] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:70:13 + --> tests/ui/unnested_or_patterns.rs:71:13 | LL | let (0 | (1 | _)) = 0; | ^^^^^^^^^^^^^ @@ -217,7 +217,7 @@ LL + let (0 | 1 | _) = 0; | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:73:16 + --> tests/ui/unnested_or_patterns.rs:74:16 | LL | if let (0 | (1 | _)) = 0 {} | ^^^^^^^^^^^^^ @@ -229,7 +229,7 @@ LL + if let (0 | 1 | _) = 0 {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:77:20 + --> tests/ui/unnested_or_patterns.rs:78:20 | LL | fn or_in_param((x | (x | x)): i32) {} | ^^^^^^^^^^^^^ @@ -240,5 +240,17 @@ LL - fn or_in_param((x | (x | x)): i32) {} LL + fn or_in_param((x | x | x): i32) {} | -error: aborting due to 20 previous errors +error: unnested or-patterns + --> tests/ui/unnested_or_patterns.rs:94:12 + | +LL | if let S { y, x: 0 } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL - if let S { y, x: 0 } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} +LL + if let S { y, x: 0 | 1 } = (S { x: 0, y: 1 }) {} + | + +error: aborting due to 21 previous errors diff --git a/src/tools/clippy/tests/ui/unused_unit.edition2021.fixed b/src/tools/clippy/tests/ui/unused_unit.edition2021.fixed index def8ef86e3c..8e12bd2c8c7 100644 --- a/src/tools/clippy/tests/ui/unused_unit.edition2021.fixed +++ b/src/tools/clippy/tests/ui/unused_unit.edition2021.fixed @@ -127,14 +127,10 @@ mod issue14577 { trait Unit {} impl Unit for () {} - fn run<R: Unit>(f: impl FnOnce() -> R) { - f(); - } - #[allow(dependency_on_unit_never_type_fallback)] fn bar() { - run(|| { todo!() }); //~[edition2021]^ unused_unit + panic!() } struct UnitStruct; @@ -150,3 +146,24 @@ mod pr14962 { type UnusedParensButNoUnit = Box<dyn (Fn())>; } + +mod issue15035 { + + trait Convert<T> { + fn from(value: T) -> Self; + } + + impl Convert<u64> for () { + fn from(_value: u64) -> Self {} + } + + fn handle<T: Convert<u64>>(value: u64) -> T { + Convert::from(value) + } + + pub fn f() -> Option<bool> { + let result: Result<bool, u64> = Err(42); + // the `-> ()` is required for the inference of `handle`'s return type + result.map_err(|err| -> () { handle(err) }).ok() + } +} diff --git a/src/tools/clippy/tests/ui/unused_unit.edition2021.stderr b/src/tools/clippy/tests/ui/unused_unit.edition2021.stderr index 13cc20d4d7a..9ad3c2df915 100644 --- a/src/tools/clippy/tests/ui/unused_unit.edition2021.stderr +++ b/src/tools/clippy/tests/ui/unused_unit.edition2021.stderr @@ -119,10 +119,10 @@ LL | fn test3()-> (){} | ^^^^^ help: remove the `-> ()` error: unneeded unit return type - --> tests/ui/unused_unit.rs:136:15 + --> tests/ui/unused_unit.rs:131:13 | -LL | run(|| -> () { todo!() }); - | ^^^^^^ help: remove the `-> ()` +LL | fn bar() -> () { + | ^^^^^^ help: remove the `-> ()` error: aborting due to 20 previous errors diff --git a/src/tools/clippy/tests/ui/unused_unit.edition2024.fixed b/src/tools/clippy/tests/ui/unused_unit.edition2024.fixed index f908b958b19..688d2fe9afa 100644 --- a/src/tools/clippy/tests/ui/unused_unit.edition2024.fixed +++ b/src/tools/clippy/tests/ui/unused_unit.edition2024.fixed @@ -127,14 +127,10 @@ mod issue14577 { trait Unit {} impl Unit for () {} - fn run<R: Unit>(f: impl FnOnce() -> R) { - f(); - } - #[allow(dependency_on_unit_never_type_fallback)] - fn bar() { - run(|| -> () { todo!() }); + fn bar() -> () { //~[edition2021]^ unused_unit + panic!() } struct UnitStruct; @@ -150,3 +146,24 @@ mod pr14962 { type UnusedParensButNoUnit = Box<dyn (Fn())>; } + +mod issue15035 { + + trait Convert<T> { + fn from(value: T) -> Self; + } + + impl Convert<u64> for () { + fn from(_value: u64) -> Self {} + } + + fn handle<T: Convert<u64>>(value: u64) -> T { + Convert::from(value) + } + + pub fn f() -> Option<bool> { + let result: Result<bool, u64> = Err(42); + // the `-> ()` is required for the inference of `handle`'s return type + result.map_err(|err| -> () { handle(err) }).ok() + } +} diff --git a/src/tools/clippy/tests/ui/unused_unit.rs b/src/tools/clippy/tests/ui/unused_unit.rs index 7298ec40cc2..31e980f2655 100644 --- a/src/tools/clippy/tests/ui/unused_unit.rs +++ b/src/tools/clippy/tests/ui/unused_unit.rs @@ -127,14 +127,10 @@ mod issue14577 { trait Unit {} impl Unit for () {} - fn run<R: Unit>(f: impl FnOnce() -> R) { - f(); - } - #[allow(dependency_on_unit_never_type_fallback)] - fn bar() { - run(|| -> () { todo!() }); + fn bar() -> () { //~[edition2021]^ unused_unit + panic!() } struct UnitStruct; @@ -150,3 +146,24 @@ mod pr14962 { type UnusedParensButNoUnit = Box<dyn (Fn())>; } + +mod issue15035 { + + trait Convert<T> { + fn from(value: T) -> Self; + } + + impl Convert<u64> for () { + fn from(_value: u64) -> Self {} + } + + fn handle<T: Convert<u64>>(value: u64) -> T { + Convert::from(value) + } + + pub fn f() -> Option<bool> { + let result: Result<bool, u64> = Err(42); + // the `-> ()` is required for the inference of `handle`'s return type + result.map_err(|err| -> () { handle(err) }).ok() + } +} diff --git a/src/tools/clippy/tests/ui/unwrap_in_result.rs b/src/tools/clippy/tests/ui/unwrap_in_result.rs index 4e872c67b42..70c28fe54f3 100644 --- a/src/tools/clippy/tests/ui/unwrap_in_result.rs +++ b/src/tools/clippy/tests/ui/unwrap_in_result.rs @@ -1,4 +1,5 @@ #![warn(clippy::unwrap_in_result)] +#![allow(clippy::ok_expect)] struct A; @@ -20,10 +21,9 @@ impl A { // should be detected fn bad_divisible_by_3(i_str: String) -> Result<bool, String> { - //~^ unwrap_in_result - // checks whether a string represents a number divisible by 3 let i = i_str.parse::<i32>().unwrap(); + //~^ unwrap_in_result if i % 3 == 0 { Ok(true) } else { @@ -32,9 +32,8 @@ impl A { } fn example_option_expect(i_str: String) -> Option<bool> { + let i = i_str.parse::<i32>().ok().expect("not a number"); //~^ unwrap_in_result - - let i = i_str.parse::<i32>().expect("not a number"); if i % 3 == 0 { return Some(true); } @@ -42,13 +41,66 @@ impl A { } fn in_closure(a: Option<bool>) -> Option<bool> { - //~^ unwrap_in_result + // No lint inside a closure let c = || a.unwrap(); - Some(c()) + + // But lint outside + let a = c().then_some(true); + let _ = a.unwrap(); + //~^ unwrap_in_result + + None } + + const fn in_const_inside_fn() -> bool { + const A: bool = { + const fn inner(b: Option<bool>) -> Option<bool> { + Some(b.unwrap()) + //~^ unwrap_in_result + } + + // No lint inside `const` + inner(Some(true)).unwrap() + }; + A + } + + fn in_static_inside_fn() -> bool { + static A: bool = { + const fn inner(b: Option<bool>) -> Option<bool> { + Some(b.unwrap()) + //~^ unwrap_in_result + } + + // No lint inside `static` + inner(Some(true)).unwrap() + }; + A + } +} + +macro_rules! mac { + () => { + Option::unwrap(Some(3)) + }; +} + +fn type_relative_unwrap() -> Option<()> { + _ = Option::unwrap(Some(3)); + //~^ unwrap_in_result + + // Do not lint macro output + _ = mac!(); + + None } -fn main() { - A::bad_divisible_by_3("3".to_string()); - A::good_divisible_by_3("3".to_string()); +fn main() -> Result<(), ()> { + A::bad_divisible_by_3("3".to_string()).unwrap(); + //~^ unwrap_in_result + A::good_divisible_by_3("3".to_string()).unwrap(); + //~^ unwrap_in_result + Result::unwrap(A::good_divisible_by_3("3".to_string())); + //~^ unwrap_in_result + Ok(()) } diff --git a/src/tools/clippy/tests/ui/unwrap_in_result.stderr b/src/tools/clippy/tests/ui/unwrap_in_result.stderr index 5e3eab813e0..804b44246dc 100644 --- a/src/tools/clippy/tests/ui/unwrap_in_result.stderr +++ b/src/tools/clippy/tests/ui/unwrap_in_result.stderr @@ -1,55 +1,107 @@ -error: used unwrap or expect in a function that returns result or option - --> tests/ui/unwrap_in_result.rs:22:5 - | -LL | / fn bad_divisible_by_3(i_str: String) -> Result<bool, String> { -... | -LL | | } - | |_____^ - | - = help: unwrap and expect should not be used in a function that returns result or option -note: potential non-recoverable error(s) - --> tests/ui/unwrap_in_result.rs:26:17 +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:25:17 | LL | let i = i_str.parse::<i32>().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:23:45 + | +LL | fn bad_divisible_by_3(i_str: String) -> Result<bool, String> { + | ^^^^^^^^^^^^^^^^^^^^ + = help: consider using the `?` operator or calling the `.map_err()` method = note: `-D clippy::unwrap-in-result` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unwrap_in_result)]` -error: used unwrap or expect in a function that returns result or option - --> tests/ui/unwrap_in_result.rs:34:5 +error: `expect` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:35:17 + | +LL | let i = i_str.parse::<i32>().ok().expect("not a number"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:34:48 + | +LL | fn example_option_expect(i_str: String) -> Option<bool> { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:49:17 + | +LL | let _ = a.unwrap(); + | ^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:43:39 + | +LL | fn in_closure(a: Option<bool>) -> Option<bool> { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:58:22 | -LL | / fn example_option_expect(i_str: String) -> Option<bool> { -LL | | -LL | | -LL | | let i = i_str.parse::<i32>().expect("not a number"); -... | -LL | | None -LL | | } - | |_____^ +LL | Some(b.unwrap()) + | ^^^^^^^^^^ | - = help: unwrap and expect should not be used in a function that returns result or option -note: potential non-recoverable error(s) - --> tests/ui/unwrap_in_result.rs:37:17 +note: in this function signature + --> tests/ui/unwrap_in_result.rs:57:48 | -LL | let i = i_str.parse::<i32>().expect("not a number"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | const fn inner(b: Option<bool>) -> Option<bool> { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator -error: used unwrap or expect in a function that returns result or option - --> tests/ui/unwrap_in_result.rs:44:5 +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:71:22 | -LL | / fn in_closure(a: Option<bool>) -> Option<bool> { -LL | | -LL | | let c = || a.unwrap(); -LL | | Some(c()) -LL | | } - | |_____^ +LL | Some(b.unwrap()) + | ^^^^^^^^^^ | - = help: unwrap and expect should not be used in a function that returns result or option -note: potential non-recoverable error(s) - --> tests/ui/unwrap_in_result.rs:46:20 +note: in this function signature + --> tests/ui/unwrap_in_result.rs:70:48 + | +LL | const fn inner(b: Option<bool>) -> Option<bool> { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:89:9 + | +LL | _ = Option::unwrap(Some(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:88:30 + | +LL | fn type_relative_unwrap() -> Option<()> { + | ^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:99:5 + | +LL | A::bad_divisible_by_3("3".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:98:14 + | +LL | fn main() -> Result<(), ()> { + | ^^^^^^^^^^^^^^ + = help: consider using the `?` operator or calling the `.map_err()` method + +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:101:5 + | +LL | A::good_divisible_by_3("3".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:103:5 | -LL | let c = || a.unwrap(); - | ^^^^^^^^^^ +LL | Result::unwrap(A::good_divisible_by_3("3".to_string())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 3 previous errors +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/vec.fixed b/src/tools/clippy/tests/ui/vec.fixed index f360a8afadf..55742459c92 100644 --- a/src/tools/clippy/tests/ui/vec.fixed +++ b/src/tools/clippy/tests/ui/vec.fixed @@ -242,3 +242,12 @@ fn issue_12101() { for a in &[1, 2] {} //~^ useless_vec } + +fn issue_14531() { + // The lint used to suggest using an array rather than a reference to a slice. + + fn requires_ref_slice(v: &[()]) {} + let v = &[]; + //~^ useless_vec + requires_ref_slice(v); +} diff --git a/src/tools/clippy/tests/ui/vec.rs b/src/tools/clippy/tests/ui/vec.rs index a779d33557c..fbf7131323c 100644 --- a/src/tools/clippy/tests/ui/vec.rs +++ b/src/tools/clippy/tests/ui/vec.rs @@ -242,3 +242,12 @@ fn issue_12101() { for a in &(vec![1, 2]) {} //~^ useless_vec } + +fn issue_14531() { + // The lint used to suggest using an array rather than a reference to a slice. + + fn requires_ref_slice(v: &[()]) {} + let v = &vec![]; + //~^ useless_vec + requires_ref_slice(v); +} diff --git a/src/tools/clippy/tests/ui/vec.stderr b/src/tools/clippy/tests/ui/vec.stderr index 806d6617200..d16c8a8944a 100644 --- a/src/tools/clippy/tests/ui/vec.stderr +++ b/src/tools/clippy/tests/ui/vec.stderr @@ -127,5 +127,11 @@ error: useless use of `vec!` LL | for a in &(vec![1, 2]) {} | ^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` -error: aborting due to 21 previous errors +error: useless use of `vec!` + --> tests/ui/vec.rs:250:13 + | +LL | let v = &vec![]; + | ^^^^^^^ help: you can use a slice directly: `&[]` + +error: aborting due to 22 previous errors diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml index 805baf2af6d..7b19f8658c0 100644 --- a/src/tools/clippy/triagebot.toml +++ b/src/tools/clippy/triagebot.toml @@ -49,11 +49,18 @@ new_pr = true # These labels are set when there are unresolved concerns, removed otherwise labels = ["S-waiting-on-concerns"] +# Show differences when a PR is rebased +[range-diff] + +# Amend a review to include a link to what was changed since the review +[review-changes-since] + [assign] contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md" users_on_vacation = [ "matthiaskrgr", "Manishearth", + "flip1995", ] [assign.owners] diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 89b1b4f84b6..62fdee98735 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -667,6 +667,10 @@ pub struct Config { /// to avoid `!nocapture` double-negatives. pub nocapture: bool, + /// True if the experimental new output-capture implementation should be + /// used, avoiding the need for `#![feature(internal_output_capture)]`. + pub new_output_capture: bool, + /// Needed both to construct [`build_helper::git::GitConfig`]. pub nightly_branch: String, pub git_merge_commit_email: String, @@ -784,6 +788,7 @@ impl Config { builtin_cfg_names: Default::default(), supported_crate_types: Default::default(), nocapture: Default::default(), + new_output_capture: Default::default(), nightly_branch: Default::default(), git_merge_commit_email: Default::default(), profiler_runtime: Default::default(), diff --git a/src/tools/compiletest/src/executor.rs b/src/tools/compiletest/src/executor.rs index 5519ef1af1f..b0dc24798b6 100644 --- a/src/tools/compiletest/src/executor.rs +++ b/src/tools/compiletest/src/executor.rs @@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex, mpsc}; use std::{env, hint, io, mem, panic, thread}; use crate::common::{Config, TestPaths}; +use crate::output_capture::{self, ConsoleOut}; use crate::panic_hook; mod deadline; @@ -120,28 +121,28 @@ fn run_test_inner( runnable_test: RunnableTest, completion_sender: mpsc::Sender<TestCompletion>, ) { - let is_capture = !runnable_test.config.nocapture; + let capture = CaptureKind::for_config(&runnable_test.config); // Install a panic-capture buffer for use by the custom panic hook. - if is_capture { + if capture.should_set_panic_hook() { panic_hook::set_capture_buf(Default::default()); } - let capture_buf = is_capture.then(|| Arc::new(Mutex::new(vec![]))); - if let Some(capture_buf) = &capture_buf { - io::set_output_capture(Some(Arc::clone(capture_buf))); + if let CaptureKind::Old { ref buf } = capture { + io::set_output_capture(Some(Arc::clone(buf))); } - let panic_payload = panic::catch_unwind(move || runnable_test.run()).err(); + let stdout = capture.stdout(); + let stderr = capture.stderr(); + + let panic_payload = panic::catch_unwind(move || runnable_test.run(stdout, stderr)).err(); if let Some(panic_buf) = panic_hook::take_capture_buf() { let panic_buf = panic_buf.lock().unwrap_or_else(|e| e.into_inner()); - // For now, forward any captured panic message to (captured) stderr. - // FIXME(Zalathar): Once we have our own output-capture buffer for - // non-panic output, append the panic message to that buffer instead. - eprint!("{panic_buf}"); + // Forward any captured panic message to (captured) stderr. + write!(stderr, "{panic_buf}"); } - if is_capture { + if matches!(capture, CaptureKind::Old { .. }) { io::set_output_capture(None); } @@ -152,11 +153,70 @@ fn run_test_inner( TestOutcome::Failed { message: Some("test did not panic as expected") } } }; - let stdout = capture_buf.map(|mutex| mutex.lock().unwrap_or_else(|e| e.into_inner()).to_vec()); + let stdout = capture.into_inner(); completion_sender.send(TestCompletion { id, outcome, stdout }).unwrap(); } +enum CaptureKind { + /// Do not capture test-runner output, for `--no-capture`. + /// + /// (This does not affect `rustc` and other subprocesses spawned by test + /// runners, whose output is always captured.) + None, + + /// Use the old output-capture implementation, which relies on the unstable + /// library feature `#![feature(internal_output_capture)]`. + Old { buf: Arc<Mutex<Vec<u8>>> }, + + /// Use the new output-capture implementation, which only uses stable Rust. + New { buf: output_capture::CaptureBuf }, +} + +impl CaptureKind { + fn for_config(config: &Config) -> Self { + if config.nocapture { + Self::None + } else if config.new_output_capture { + Self::New { buf: output_capture::CaptureBuf::new() } + } else { + // Create a capure buffer for `io::set_output_capture`. + Self::Old { buf: Default::default() } + } + } + + fn should_set_panic_hook(&self) -> bool { + match self { + Self::None => false, + Self::Old { .. } => true, + Self::New { .. } => true, + } + } + + fn stdout(&self) -> &dyn ConsoleOut { + self.capture_buf_or(&output_capture::Stdout) + } + + fn stderr(&self) -> &dyn ConsoleOut { + self.capture_buf_or(&output_capture::Stderr) + } + + fn capture_buf_or<'a>(&'a self, fallback: &'a dyn ConsoleOut) -> &'a dyn ConsoleOut { + match self { + Self::None | Self::Old { .. } => fallback, + Self::New { buf } => buf, + } + } + + fn into_inner(self) -> Option<Vec<u8>> { + match self { + Self::None => None, + Self::Old { buf } => Some(buf.lock().unwrap_or_else(|e| e.into_inner()).to_vec()), + Self::New { buf } => Some(buf.into_inner().into()), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] struct TestId(usize); @@ -174,10 +234,12 @@ impl RunnableTest { Self { config, testpaths, revision } } - fn run(&self) { + fn run(&self, stdout: &dyn ConsoleOut, stderr: &dyn ConsoleOut) { __rust_begin_short_backtrace(|| { crate::runtest::run( Arc::clone(&self.config), + stdout, + stderr, &self.testpaths, self.revision.as_deref(), ); diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index fa84691a46f..f647f96a9bf 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -15,6 +15,7 @@ pub mod directives; pub mod errors; mod executor; mod json; +mod output_capture; mod panic_hook; mod raise_fd_limit; mod read2; @@ -177,6 +178,12 @@ pub fn parse_config(args: Vec<String>) -> Config { // FIXME: Temporarily retained so we can point users to `--no-capture` .optflag("", "nocapture", "") .optflag("", "no-capture", "don't capture stdout/stderr of tests") + .optopt( + "N", + "new-output-capture", + "enables or disables the new output-capture implementation", + "off|on", + ) .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target") .optflag("h", "help", "show this message") .reqopt("", "channel", "current Rust channel", "CHANNEL") @@ -461,6 +468,14 @@ pub fn parse_config(args: Vec<String>) -> Config { supported_crate_types: OnceLock::new(), nocapture: matches.opt_present("no-capture"), + new_output_capture: { + let value = matches + .opt_str("new-output-capture") + .or_else(|| env::var("COMPILETEST_NEW_OUTPUT_CAPTURE").ok()) + .unwrap_or_else(|| "off".to_owned()); + parse_bool_option(&value) + .unwrap_or_else(|| panic!("unknown `--new-output-capture` value `{value}` given")) + }, nightly_branch: matches.opt_str("nightly-branch").unwrap(), git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(), @@ -476,6 +491,19 @@ pub fn parse_config(args: Vec<String>) -> Config { } } +/// Parses the same set of boolean values accepted by rustc command-line arguments. +/// +/// Accepting all of these values is more complicated than just picking one +/// pair, but has the advantage that contributors who are used to rustc +/// shouldn't have to think about which values are legal. +fn parse_bool_option(value: &str) -> Option<bool> { + match value { + "off" | "no" | "n" | "false" => Some(false), + "on" | "yes" | "y" | "true" => Some(true), + _ => None, + } +} + pub fn opt_str(maybestr: &Option<String>) -> &str { match *maybestr { None => "(none)", diff --git a/src/tools/compiletest/src/output_capture.rs b/src/tools/compiletest/src/output_capture.rs new file mode 100644 index 00000000000..de1aea11ade --- /dev/null +++ b/src/tools/compiletest/src/output_capture.rs @@ -0,0 +1,52 @@ +use std::fmt; +use std::panic::RefUnwindSafe; +use std::sync::Mutex; + +pub trait ConsoleOut: fmt::Debug + RefUnwindSafe { + fn write_fmt(&self, args: fmt::Arguments<'_>); +} + +#[derive(Debug)] +pub(crate) struct Stdout; + +impl ConsoleOut for Stdout { + fn write_fmt(&self, args: fmt::Arguments<'_>) { + print!("{args}"); + } +} + +#[derive(Debug)] +pub(crate) struct Stderr; + +impl ConsoleOut for Stderr { + fn write_fmt(&self, args: fmt::Arguments<'_>) { + eprint!("{args}"); + } +} + +pub(crate) struct CaptureBuf { + inner: Mutex<String>, +} + +impl CaptureBuf { + pub(crate) fn new() -> Self { + Self { inner: Mutex::new(String::new()) } + } + + pub(crate) fn into_inner(self) -> String { + self.inner.into_inner().unwrap_or_else(|e| e.into_inner()) + } +} + +impl fmt::Debug for CaptureBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CaptureBuf").finish_non_exhaustive() + } +} + +impl ConsoleOut for CaptureBuf { + fn write_fmt(&self, args: fmt::Arguments<'_>) { + let mut s = self.inner.lock().unwrap_or_else(|e| e.into_inner()); + <String as fmt::Write>::write_fmt(&mut s, args).unwrap(); + } +} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 867624cc8fa..89fb8eb4357 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -23,6 +23,7 @@ use crate::common::{ }; use crate::directives::TestProps; use crate::errors::{Error, ErrorKind, load_errors}; +use crate::output_capture::ConsoleOut; use crate::read2::{Truncated, read2_abbreviated}; use crate::runtest::compute_diff::{DiffLine, make_diff, write_diff, write_filtered_diff}; use crate::util::{Utf8PathBufExt, add_dylib_path, static_regex}; @@ -108,7 +109,13 @@ fn dylib_name(name: &str) -> String { format!("{}{name}.{}", std::env::consts::DLL_PREFIX, std::env::consts::DLL_EXTENSION) } -pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) { +pub fn run( + config: Arc<Config>, + stdout: &dyn ConsoleOut, + stderr: &dyn ConsoleOut, + testpaths: &TestPaths, + revision: Option<&str>, +) { match &*config.target { "arm-linux-androideabi" | "armv7-linux-androideabi" @@ -131,7 +138,7 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) { if config.verbose { // We're going to be dumping a lot of info. Start on a new line. - print!("\n\n"); + write!(stdout, "\n\n"); } debug!("running {}", testpaths.file); let mut props = TestProps::from_file(&testpaths.file, revision, &config); @@ -143,7 +150,7 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) { props.incremental_dir = Some(incremental_dir(&config, testpaths, revision)); } - let cx = TestCx { config: &config, props: &props, testpaths, revision }; + let cx = TestCx { config: &config, stdout, stderr, props: &props, testpaths, revision }; if let Err(e) = create_dir_all(&cx.output_base_dir()) { panic!("failed to create output base directory {}: {e}", cx.output_base_dir()); @@ -162,6 +169,8 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) { revision_props.incremental_dir = props.incremental_dir.clone(); let rev_cx = TestCx { config: &config, + stdout, + stderr, props: &revision_props, testpaths, revision: Some(revision), @@ -212,6 +221,8 @@ pub fn compute_stamp_hash(config: &Config) -> String { #[derive(Copy, Clone, Debug)] struct TestCx<'test> { config: &'test Config, + stdout: &'test dyn ConsoleOut, + stderr: &'test dyn ConsoleOut, props: &'test TestProps, testpaths: &'test TestPaths, revision: Option<&'test str>, @@ -603,7 +614,8 @@ impl<'test> TestCx<'test> { ); } else { for pattern in missing_patterns { - println!( + writeln!( + self.stdout, "\n{prefix}: error pattern '{pattern}' not found!", prefix = self.error_prefix() ); @@ -783,7 +795,8 @@ impl<'test> TestCx<'test> { }; format!("{file_name}:{line_num}{opt_col_num}") }; - let print_error = |e| println!("{}: {}: {}", line_str(e), e.kind, e.msg.cyan()); + let print_error = + |e| writeln!(self.stdout, "{}: {}: {}", line_str(e), e.kind, e.msg.cyan()); let push_suggestion = |suggestions: &mut Vec<_>, e: &Error, kind, line, msg, color, rank| { let mut ret = String::new(); @@ -811,7 +824,7 @@ impl<'test> TestCx<'test> { if let Some(&(_, top_rank)) = suggestions.first() { for (suggestion, rank) in suggestions { if rank == top_rank { - println!(" {} {suggestion}", prefix.color(color)); + writeln!(self.stdout, " {} {suggestion}", prefix.color(color)); } } } @@ -824,7 +837,8 @@ impl<'test> TestCx<'test> { // - only known line - meh, but suggested // - others are not worth suggesting if !unexpected.is_empty() { - println!( + writeln!( + self.stdout, "\n{prefix}: {n} diagnostics reported in JSON output but not expected in test file", prefix = self.error_prefix(), n = unexpected.len(), @@ -858,7 +872,8 @@ impl<'test> TestCx<'test> { } } if !not_found.is_empty() { - println!( + writeln!( + self.stdout, "\n{prefix}: {n} diagnostics expected in test file but not reported in JSON output", prefix = self.error_prefix(), n = not_found.len(), @@ -978,6 +993,8 @@ impl<'test> TestCx<'test> { self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config); let aux_cx = TestCx { config: self.config, + stdout: self.stdout, + stderr: self.stderr, props: &props_for_aux, testpaths: &aux_testpaths, revision: self.revision, @@ -1343,6 +1360,8 @@ impl<'test> TestCx<'test> { let aux_output = TargetLocation::ThisDirectory(aux_dir.clone()); let aux_cx = TestCx { config: self.config, + stdout: self.stdout, + stderr: self.stderr, props: &aux_props, testpaths: &aux_testpaths, revision: self.revision, @@ -1948,11 +1967,11 @@ impl<'test> TestCx<'test> { } else { path.file_name().unwrap().into() }; - println!("------{proc_name} stdout------------------------------"); - println!("{}", out); - println!("------{proc_name} stderr------------------------------"); - println!("{}", err); - println!("------------------------------------------"); + writeln!(self.stdout, "------{proc_name} stdout------------------------------"); + writeln!(self.stdout, "{}", out); + writeln!(self.stdout, "------{proc_name} stderr------------------------------"); + writeln!(self.stdout, "{}", err); + writeln!(self.stdout, "------------------------------------------"); } fn dump_output_file(&self, out: &str, extension: &str) { @@ -2014,7 +2033,7 @@ impl<'test> TestCx<'test> { debug!("{message}"); if self.config.verbose { // Note: `./x test ... --verbose --no-capture` is needed to see this print. - println!("{message}"); + writeln!(self.stdout, "{message}"); } } @@ -2030,7 +2049,7 @@ impl<'test> TestCx<'test> { #[track_caller] fn fatal(&self, err: &str) -> ! { - println!("\n{prefix}: {err}", prefix = self.error_prefix()); + writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix()); error!("fatal error, panic: {:?}", err); panic!("fatal error"); } @@ -2048,15 +2067,15 @@ impl<'test> TestCx<'test> { proc_res: &ProcRes, callback_before_unwind: impl FnOnce(), ) -> ! { - println!("\n{prefix}: {err}", prefix = self.error_prefix()); + writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix()); // Some callers want to print additional notes after the main error message. if let Some(note) = extra_note { - println!("{note}"); + writeln!(self.stdout, "{note}"); } // Print the details and output of the subprocess that caused this test to fail. - println!("{}", proc_res.format_info()); + writeln!(self.stdout, "{}", proc_res.format_info()); // Some callers want print more context or show a custom diff before the unwind occurs. callback_before_unwind(); @@ -2126,7 +2145,7 @@ impl<'test> TestCx<'test> { if !self.config.has_html_tidy { return; } - println!("info: generating a diff against nightly rustdoc"); + writeln!(self.stdout, "info: generating a diff against nightly rustdoc"); let suffix = self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly"); @@ -2162,7 +2181,7 @@ impl<'test> TestCx<'test> { let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths); if !proc_res.status.success() { - eprintln!("failed to run nightly rustdoc"); + writeln!(self.stderr, "failed to run nightly rustdoc"); return; } @@ -2207,6 +2226,7 @@ impl<'test> TestCx<'test> { let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id()); if !write_filtered_diff( + self, &diff_filename, out_dir, &compare_dir, @@ -2227,7 +2247,7 @@ impl<'test> TestCx<'test> { if let Some(pager) = pager { let pager = pager.trim(); if self.config.verbose { - eprintln!("using pager {}", pager); + writeln!(self.stderr, "using pager {}", pager); } let output = Command::new(pager) // disable paging; we want this to be non-interactive @@ -2238,8 +2258,8 @@ impl<'test> TestCx<'test> { .output() .unwrap(); assert!(output.status.success()); - println!("{}", String::from_utf8_lossy(&output.stdout)); - eprintln!("{}", String::from_utf8_lossy(&output.stderr)); + writeln!(self.stdout, "{}", String::from_utf8_lossy(&output.stdout)); + writeln!(self.stderr, "{}", String::from_utf8_lossy(&output.stderr)); } else { warning!("no pager configured, falling back to unified diff"); help!( @@ -2254,7 +2274,7 @@ impl<'test> TestCx<'test> { match diff.read_until(b'\n', &mut line) { Ok(0) => break, Ok(_) => {} - Err(e) => eprintln!("ERROR: {:?}", e), + Err(e) => writeln!(self.stderr, "ERROR: {:?}", e), } match String::from_utf8(line.clone()) { Ok(line) => { @@ -2802,11 +2822,11 @@ impl<'test> TestCx<'test> { if let Err(err) = fs::write(&actual_path, &actual) { self.fatal(&format!("failed to write {stream} to `{actual_path}`: {err}",)); } - println!("Saved the actual {stream} to `{actual_path}`"); + writeln!(self.stdout, "Saved the actual {stream} to `{actual_path}`"); if !self.config.bless { if expected.is_empty() { - println!("normalized {}:\n{}\n", stream, actual); + writeln!(self.stdout, "normalized {}:\n{}\n", stream, actual); } else { self.show_diff( stream, @@ -2830,14 +2850,15 @@ impl<'test> TestCx<'test> { if let Err(err) = fs::write(&expected_path, &actual) { self.fatal(&format!("failed to write {stream} to `{expected_path}`: {err}")); } - println!( + writeln!( + self.stdout, "Blessing the {stream} of `{test_name}` as `{expected_path}`", test_name = self.testpaths.file ); } } - println!("\nThe actual {stream} differed from the expected {stream}"); + writeln!(self.stdout, "\nThe actual {stream} differed from the expected {stream}"); if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed } } @@ -2852,7 +2873,7 @@ impl<'test> TestCx<'test> { actual: &str, actual_unnormalized: &str, ) { - eprintln!("diff of {stream}:\n"); + writeln!(self.stderr, "diff of {stream}:\n"); if let Some(diff_command) = self.config.diff_command.as_deref() { let mut args = diff_command.split_whitespace(); let name = args.next().unwrap(); @@ -2864,11 +2885,11 @@ impl<'test> TestCx<'test> { } Ok(output) => { let output = String::from_utf8_lossy(&output.stdout); - eprint!("{output}"); + write!(self.stderr, "{output}"); } } } else { - eprint!("{}", write_diff(expected, actual, 3)); + write!(self.stderr, "{}", write_diff(expected, actual, 3)); } // NOTE: argument order is important, we need `actual` to be on the left so the line number match up when we compare it to `actual_unnormalized` below. @@ -2906,9 +2927,16 @@ impl<'test> TestCx<'test> { && !mismatches_unnormalized.is_empty() && !mismatches_normalized.is_empty() { - eprintln!("Note: some mismatched output was normalized before being compared"); + writeln!( + self.stderr, + "Note: some mismatched output was normalized before being compared" + ); // FIXME: respect diff_command - eprint!("{}", write_diff(&mismatches_unnormalized, &mismatches_normalized, 0)); + write!( + self.stderr, + "{}", + write_diff(&mismatches_unnormalized, &mismatches_normalized, 0) + ); } } @@ -2986,7 +3014,7 @@ impl<'test> TestCx<'test> { fs::create_dir_all(&incremental_dir).unwrap(); if self.config.verbose { - println!("init_incremental_test: incremental_dir={incremental_dir}"); + writeln!(self.stdout, "init_incremental_test: incremental_dir={incremental_dir}"); } } } diff --git a/src/tools/compiletest/src/runtest/codegen_units.rs b/src/tools/compiletest/src/runtest/codegen_units.rs index 44ddcb1d288..16c251c3c9e 100644 --- a/src/tools/compiletest/src/runtest/codegen_units.rs +++ b/src/tools/compiletest/src/runtest/codegen_units.rs @@ -62,13 +62,13 @@ impl TestCx<'_> { if !missing.is_empty() { missing.sort(); - println!("\nThese items should have been contained but were not:\n"); + writeln!(self.stdout, "\nThese items should have been contained but were not:\n"); for item in &missing { - println!("{}", item); + writeln!(self.stdout, "{}", item); } - println!("\n"); + writeln!(self.stdout, "\n"); } if !unexpected.is_empty() { @@ -78,24 +78,32 @@ impl TestCx<'_> { sorted }; - println!("\nThese items were contained but should not have been:\n"); + writeln!(self.stdout, "\nThese items were contained but should not have been:\n"); for item in sorted { - println!("{}", item); + writeln!(self.stdout, "{}", item); } - println!("\n"); + writeln!(self.stdout, "\n"); } if !wrong_cgus.is_empty() { wrong_cgus.sort_by_key(|pair| pair.0.name.clone()); - println!("\nThe following items were assigned to wrong codegen units:\n"); + writeln!(self.stdout, "\nThe following items were assigned to wrong codegen units:\n"); for &(ref expected_item, ref actual_item) in &wrong_cgus { - println!("{}", expected_item.name); - println!(" expected: {}", codegen_units_to_str(&expected_item.codegen_units)); - println!(" actual: {}", codegen_units_to_str(&actual_item.codegen_units)); - println!(); + writeln!(self.stdout, "{}", expected_item.name); + writeln!( + self.stdout, + " expected: {}", + codegen_units_to_str(&expected_item.codegen_units) + ); + writeln!( + self.stdout, + " actual: {}", + codegen_units_to_str(&actual_item.codegen_units) + ); + writeln!(self.stdout); } } diff --git a/src/tools/compiletest/src/runtest/compute_diff.rs b/src/tools/compiletest/src/runtest/compute_diff.rs index 509e7e11703..3363127b3ea 100644 --- a/src/tools/compiletest/src/runtest/compute_diff.rs +++ b/src/tools/compiletest/src/runtest/compute_diff.rs @@ -3,6 +3,8 @@ use std::fs::{File, FileType}; use camino::Utf8Path; +use crate::runtest::TestCx; + #[derive(Debug, PartialEq)] pub enum DiffLine { Context(String), @@ -112,6 +114,7 @@ pub(crate) fn write_diff(expected: &str, actual: &str, context_size: usize) -> S /// /// Returns whether any data was actually written. pub(crate) fn write_filtered_diff<Filter>( + cx: &TestCx<'_>, diff_filename: &str, out_dir: &Utf8Path, compare_dir: &Utf8Path, @@ -147,11 +150,11 @@ where } if !wrote_data { - println!("note: diff is identical to nightly rustdoc"); + writeln!(cx.stdout, "note: diff is identical to nightly rustdoc"); assert!(diff_output.metadata().unwrap().len() == 0); return false; } else if verbose { - eprintln!("printing diff:"); + writeln!(cx.stderr, "printing diff:"); let mut buf = Vec::new(); diff_output.read_to_end(&mut buf).unwrap(); std::io::stderr().lock().write_all(&mut buf).unwrap(); diff --git a/src/tools/compiletest/src/runtest/crashes.rs b/src/tools/compiletest/src/runtest/crashes.rs index da1e74b4a56..0aae7eaa39c 100644 --- a/src/tools/compiletest/src/runtest/crashes.rs +++ b/src/tools/compiletest/src/runtest/crashes.rs @@ -6,10 +6,10 @@ impl TestCx<'_> { let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm)); if std::env::var("COMPILETEST_VERBOSE_CRASHES").is_ok() { - eprintln!("{}", proc_res.status); - eprintln!("{}", proc_res.stdout); - eprintln!("{}", proc_res.stderr); - eprintln!("{}", proc_res.cmdline); + writeln!(self.stderr, "{}", proc_res.status); + writeln!(self.stderr, "{}", proc_res.stdout); + writeln!(self.stderr, "{}", proc_res.stderr); + writeln!(self.stderr, "{}", proc_res.cmdline); } // if a test does not crash, consider it an error diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs index 88d022b8bba..071c0863b7e 100644 --- a/src/tools/compiletest/src/runtest/debuginfo.rs +++ b/src/tools/compiletest/src/runtest/debuginfo.rs @@ -245,7 +245,7 @@ impl TestCx<'_> { cmdline, }; if adb.kill().is_err() { - println!("Adb process is already finished."); + writeln!(self.stdout, "Adb process is already finished."); } } else { let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc"); @@ -256,7 +256,11 @@ impl TestCx<'_> { match self.config.gdb_version { Some(version) => { - println!("NOTE: compiletest thinks it is using GDB version {}", version); + writeln!( + self.stdout, + "NOTE: compiletest thinks it is using GDB version {}", + version + ); if !self.props.disable_gdb_pretty_printers && version > extract_gdb_version("7.4").unwrap() @@ -278,7 +282,8 @@ impl TestCx<'_> { } } _ => { - println!( + writeln!( + self.stdout, "NOTE: compiletest does not know which version of \ GDB it is using" ); @@ -376,10 +381,15 @@ impl TestCx<'_> { match self.config.lldb_version { Some(ref version) => { - println!("NOTE: compiletest thinks it is using LLDB version {}", version); + writeln!( + self.stdout, + "NOTE: compiletest thinks it is using LLDB version {}", + version + ); } _ => { - println!( + writeln!( + self.stdout, "NOTE: compiletest does not know which version of \ LLDB it is using" ); diff --git a/src/tools/compiletest/src/runtest/incremental.rs b/src/tools/compiletest/src/runtest/incremental.rs index 90cff6bab4d..44eb80300c3 100644 --- a/src/tools/compiletest/src/runtest/incremental.rs +++ b/src/tools/compiletest/src/runtest/incremental.rs @@ -30,7 +30,7 @@ impl TestCx<'_> { assert!(incremental_dir.exists(), "init_incremental_test failed to create incremental dir"); if self.config.verbose { - print!("revision={:?} props={:#?}", revision, self.props); + write!(self.stdout, "revision={:?} props={:#?}", revision, self.props); } if revision.starts_with("cpass") { diff --git a/src/tools/compiletest/src/runtest/mir_opt.rs b/src/tools/compiletest/src/runtest/mir_opt.rs index 55043bf4bc2..94487926383 100644 --- a/src/tools/compiletest/src/runtest/mir_opt.rs +++ b/src/tools/compiletest/src/runtest/mir_opt.rs @@ -80,7 +80,7 @@ impl TestCx<'_> { } let expected_string = fs::read_to_string(&expected_file).unwrap(); if dumped_string != expected_string { - print!("{}", write_diff(&expected_string, &dumped_string, 3)); + write!(self.stdout, "{}", write_diff(&expected_string, &dumped_string, 3)); panic!( "Actual MIR output differs from expected MIR output {}", expected_file.display() diff --git a/src/tools/compiletest/src/runtest/rustdoc_json.rs b/src/tools/compiletest/src/runtest/rustdoc_json.rs index 083398f9274..b8da6e2ac52 100644 --- a/src/tools/compiletest/src/runtest/rustdoc_json.rs +++ b/src/tools/compiletest/src/runtest/rustdoc_json.rs @@ -30,8 +30,8 @@ impl TestCx<'_> { if !res.status.success() { self.fatal_proc_rec_general("jsondocck failed!", None, &res, || { - println!("Rustdoc Output:"); - println!("{}", proc_res.format_info()); + writeln!(self.stdout, "Rustdoc Output:"); + writeln!(self.stdout, "{}", proc_res.format_info()); }) } diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs index 40b0ee0a399..d683a325c86 100644 --- a/src/tools/compiletest/src/runtest/ui.rs +++ b/src/tools/compiletest/src/runtest/ui.rs @@ -115,10 +115,14 @@ impl TestCx<'_> { } if errors > 0 { - println!("To update references, rerun the tests and pass the `--bless` flag"); + writeln!( + self.stdout, + "To update references, rerun the tests and pass the `--bless` flag" + ); let relative_path_to_file = self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap()); - println!( + writeln!( + self.stdout, "To only update this specific test, also pass `--test-args {}`", relative_path_to_file, ); diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index 1f16a672a98..558e9a58697 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -45,7 +45,7 @@ impl Utf8PathBufExt for Utf8PathBuf { /// The name of the environment variable that holds dynamic library locations. pub fn dylib_env_var() -> &'static str { - if cfg!(windows) { + if cfg!(any(windows, target_os = "cygwin")) { "PATH" } else if cfg!(target_vendor = "apple") { "DYLD_LIBRARY_PATH" diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shl.rs b/src/tools/miri/tests/fail/intrinsics/funnel_shl.rs new file mode 100644 index 00000000000..40b61f65ebe --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shl.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics, funnel_shifts)] + +fn main() { + unsafe { + std::intrinsics::unchecked_funnel_shl(1_u32, 2, 32); //~ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shl.stderr b/src/tools/miri/tests/fail/intrinsics/funnel_shl.stderr new file mode 100644 index 00000000000..fc828021b13 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shl.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `assume` called with `false` + --> tests/fail/intrinsics/funnel_shl.rs:LL:CC + | +LL | std::intrinsics::unchecked_funnel_shl(1_u32, 2, 32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/funnel_shl.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shr.rs b/src/tools/miri/tests/fail/intrinsics/funnel_shr.rs new file mode 100644 index 00000000000..95822656397 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shr.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics, funnel_shifts)] + +fn main() { + unsafe { + std::intrinsics::unchecked_funnel_shr(1_u32, 2, 32); //~ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shr.stderr b/src/tools/miri/tests/fail/intrinsics/funnel_shr.stderr new file mode 100644 index 00000000000..7361df1b4c7 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `assume` called with `false` + --> tests/fail/intrinsics/funnel_shr.rs:LL:CC + | +LL | std::intrinsics::unchecked_funnel_shr(1_u32, 2, 32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/funnel_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/intrinsics/integer.rs b/src/tools/miri/tests/pass/intrinsics/integer.rs index 13e7bd8e1b9..8727b6d3c87 100644 --- a/src/tools/miri/tests/pass/intrinsics/integer.rs +++ b/src/tools/miri/tests/pass/intrinsics/integer.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-FileCopyrightText: The Rust Project Developers (see https://thanks.rust-lang.org) -#![feature(core_intrinsics)] +#![feature(core_intrinsics, funnel_shifts)] use std::intrinsics::*; pub fn main() { @@ -143,5 +143,11 @@ pub fn main() { assert_eq!(unchecked_mul(6u8, 7), 42); assert_eq!(unchecked_mul(13, -5), -65); + + assert_eq!(unchecked_funnel_shl(1_u32, 2, 5), 32); + assert_eq!(unchecked_funnel_shl(1_u32, 2, 31), 0x80000001); + + assert_eq!(unchecked_funnel_shr(1_u32, 2, 5), 0x08000000); + assert_eq!(unchecked_funnel_shr(1_u32, 2, 31), 2); } } diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs index 31169ec5967..34d9ea92629 100644 --- a/src/tools/tidy/src/extra_checks/mod.rs +++ b/src/tools/tidy/src/extra_checks/mod.rs @@ -720,21 +720,19 @@ impl ExtraCheckArg { if !self.auto { return true; } - let ext = match self.lang { - ExtraCheckLang::Py => ".py", - ExtraCheckLang::Cpp => ".cpp", - ExtraCheckLang::Shell => ".sh", - ExtraCheckLang::Js => ".js", + let exts: &[&str] = match self.lang { + ExtraCheckLang::Py => &[".py"], + ExtraCheckLang::Cpp => &[".cpp"], + ExtraCheckLang::Shell => &[".sh"], + ExtraCheckLang::Js => &[".js", ".ts"], ExtraCheckLang::Spellcheck => { - for dir in SPELLCHECK_DIRS { - if Path::new(filepath).starts_with(dir) { - return true; - } + if SPELLCHECK_DIRS.iter().any(|dir| Path::new(filepath).starts_with(dir)) { + return true; } - return false; + &[] } }; - filepath.ends_with(ext) + exts.iter().any(|ext| filepath.ends_with(ext)) } fn has_supported_kind(&self) -> bool { diff --git a/src/tools/tidy/src/triagebot.rs b/src/tools/tidy/src/triagebot.rs index 305a0b4d264..6f25ed616fa 100644 --- a/src/tools/tidy/src/triagebot.rs +++ b/src/tools/tidy/src/triagebot.rs @@ -19,7 +19,12 @@ pub fn check(path: &Path, bad: &mut bool) { // Check [mentions."*"] sections, i.e. [mentions."compiler/rustc_const_eval/src/"] if let Some(Value::Table(mentions)) = config.get("mentions") { - for path_str in mentions.keys() { + for (entry_key, entry_val) in mentions.iter() { + // If the type is set to something other than "filename", then this is not a path. + if entry_val.get("type").is_some_and(|t| t.as_str().unwrap_or_default() != "filename") { + continue; + } + let path_str = entry_key; // Remove quotes from the path let clean_path = path_str.trim_matches('"'); let full_path = path.join(clean_path); |
