diff options
| author | Ralf Jung <post@ralfj.de> | 2025-07-10 06:57:45 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-10 06:57:45 +0000 |
| commit | 317f59dfebb3ca584c4ab7c137208bbb23601eb2 (patch) | |
| tree | 4475de7055ec501bfa7d9789536894ddafe1dc6b /src | |
| parent | a09cf9cc5a7fd4e2e19d937ef03bfeec34993a49 (diff) | |
| parent | 17b240e04f8d14e5563b6afb2ed3b842f87d1c78 (diff) | |
| download | rust-317f59dfebb3ca584c4ab7c137208bbb23601eb2.tar.gz rust-317f59dfebb3ca584c4ab7c137208bbb23601eb2.zip | |
Merge pull request #4458 from rust-lang/rustup-2025-07-10
Automatic Rustup
Diffstat (limited to 'src')
179 files changed, 3424 insertions, 855 deletions
diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index c077555b906..b05a5cc8b81 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -739,19 +739,29 @@ def configure_file(sections, top_level_keys, targets, config): def write_uncommented(target, f): + """Writes each block in 'target' that is not composed entirely of comments to 'f'. + + A block is a sequence of non-empty lines separated by empty lines. + """ block = [] - is_comment = True + + def flush(last): + # If the block is entirely made of comments, ignore it + entire_block_comments = all(ln.startswith("#") or ln == "" for ln in block) + if not entire_block_comments and len(block) > 0: + for line in block: + f.write(line + "\n") + # Required to output a newline before the start of a new section + if last: + f.write("\n") + block.clear() for line in target: block.append(line) if len(line) == 0: - if not is_comment: - for ln in block: - f.write(ln + "\n") - block = [] - is_comment = True - continue - is_comment = is_comment and line.startswith("#") + flush(last=False) + + flush(last=True) return f diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3e2bdc2d6b5..0587d21ecc2 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1369,9 +1369,7 @@ pub fn rustc_cargo_env( } // Enable rustc's env var for `rust-lld` when requested. - if builder.config.lld_enabled - && (builder.config.channel == "dev" || builder.config.channel == "nightly") - { + if builder.config.lld_enabled { cargo.env("CFG_USE_SELF_CONTAINED_LINKER", "1"); } @@ -2059,14 +2057,20 @@ impl Step for Assemble { trace!("llvm-bitcode-linker enabled, installing"); let llvm_bitcode_linker = builder.ensure(crate::core::build_steps::tool::LlvmBitcodeLinker { - compiler, + build_compiler: compiler, target: target_compiler.host, - extra_features: vec![], }); + + // Copy the llvm-bitcode-linker to the self-contained binary directory + let bindir_self_contained = builder + .sysroot(compiler) + .join(format!("lib/rustlib/{}/bin/self-contained", compiler.host)); let tool_exe = exe("llvm-bitcode-linker", target_compiler.host); + + t!(fs::create_dir_all(&bindir_self_contained)); builder.copy_link( &llvm_bitcode_linker.tool_path, - &libdir_bin.join(tool_exe), + &bindir_self_contained.join(tool_exe), FileType::Executable, ); } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 25b7e5a1b5d..8b2d65ace50 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -2375,7 +2375,7 @@ impl Step for LlvmBitcodeLinker { builder.ensure(compile::Rustc::new(compiler, target)); let llbc_linker = - builder.ensure(tool::LlvmBitcodeLinker { compiler, target, extra_features: vec![] }); + builder.ensure(tool::LlvmBitcodeLinker { build_compiler: compiler, target }); let self_contained_bin_dir = format!("lib/rustlib/{}/bin/self-contained", target.triple); diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index a5b7b22aba8..716bef3f38c 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -262,7 +262,13 @@ impl Step for Cargotest { .args(builder.config.test_args()) .env("RUSTC", builder.rustc(compiler)) .env("RUSTDOC", builder.rustdoc(compiler)); - add_rustdoc_cargo_linker_args(&mut cmd, builder, compiler.host, LldThreads::No); + add_rustdoc_cargo_linker_args( + &mut cmd, + builder, + compiler.host, + LldThreads::No, + compiler.stage, + ); cmd.delay_failure().run(builder); } } @@ -857,7 +863,7 @@ impl Step for RustdocTheme { .env("CFG_RELEASE_CHANNEL", &builder.config.channel) .env("RUSTDOC_REAL", builder.rustdoc(self.compiler)) .env("RUSTC_BOOTSTRAP", "1"); - cmd.args(linker_args(builder, self.compiler.host, LldThreads::No)); + cmd.args(linker_args(builder, self.compiler.host, LldThreads::No, self.compiler.stage)); cmd.delay_failure().run(builder); } @@ -1033,7 +1039,13 @@ impl Step for RustdocGUI { cmd.env("RUSTDOC", builder.rustdoc(self.compiler)) .env("RUSTC", builder.rustc(self.compiler)); - add_rustdoc_cargo_linker_args(&mut cmd, builder, self.compiler.host, LldThreads::No); + add_rustdoc_cargo_linker_args( + &mut cmd, + builder, + self.compiler.host, + LldThreads::No, + self.compiler.stage, + ); for path in &builder.paths { if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) { @@ -1812,7 +1824,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the } let mut hostflags = flags.clone(); - hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No)); + hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No, compiler.stage)); let mut targetflags = flags; diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index b05b34b9b22..5de1b472d79 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -910,6 +910,13 @@ impl Step for LldWrapper { tool_result } + + fn metadata(&self) -> Option<StepMetadata> { + Some( + StepMetadata::build("LldWrapper", self.target_compiler.host) + .built_by(self.build_compiler), + ) + } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -1014,9 +1021,8 @@ impl Step for RustAnalyzerProcMacroSrv { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct LlvmBitcodeLinker { - pub compiler: Compiler, + pub build_compiler: Compiler, pub target: TargetSelection, - pub extra_features: Vec<String>, } impl Step for LlvmBitcodeLinker { @@ -1032,8 +1038,9 @@ impl Step for LlvmBitcodeLinker { fn make_run(run: RunConfig<'_>) { run.builder.ensure(LlvmBitcodeLinker { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), - extra_features: Vec::new(), + build_compiler: run + .builder + .compiler(run.builder.top_stage, run.builder.config.host_target), target: run.target, }); } @@ -1043,35 +1050,22 @@ impl Step for LlvmBitcodeLinker { instrument(level = "debug", name = "LlvmBitcodeLinker::run", skip_all) )] fn run(self, builder: &Builder<'_>) -> ToolBuildResult { - let tool_result = builder.ensure(ToolBuild { - compiler: self.compiler, + builder.ensure(ToolBuild { + compiler: self.build_compiler, target: self.target, tool: "llvm-bitcode-linker", mode: Mode::ToolRustc, path: "src/tools/llvm-bitcode-linker", source_type: SourceType::InTree, - extra_features: self.extra_features, + extra_features: vec![], allow_features: "", cargo_args: Vec::new(), artifact_kind: ToolArtifactKind::Binary, - }); + }) + } - if tool_result.target_compiler.stage > 0 { - let bindir_self_contained = builder - .sysroot(tool_result.target_compiler) - .join(format!("lib/rustlib/{}/bin/self-contained", self.target.triple)); - t!(fs::create_dir_all(&bindir_self_contained)); - let bin_destination = bindir_self_contained - .join(exe("llvm-bitcode-linker", tool_result.target_compiler.host)); - builder.copy_link(&tool_result.tool_path, &bin_destination, FileType::Executable); - ToolBuildResult { - tool_path: bin_destination, - build_compiler: tool_result.build_compiler, - target_compiler: tool_result.target_compiler, - } - } else { - tool_result - } + fn metadata(&self) -> Option<StepMetadata> { + Some(StepMetadata::build("LlvmBitcodeLinker", self.target).built_by(self.build_compiler)) } } diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index deb7106f185..065d7e45e0f 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -115,7 +115,7 @@ impl Cargo { // No need to configure the target linker for these command types. Kind::Clean | Kind::Check | Kind::Suggest | Kind::Format | Kind::Setup => {} _ => { - cargo.configure_linker(builder); + cargo.configure_linker(builder, mode); } } @@ -209,7 +209,7 @@ impl Cargo { // FIXME(onur-ozkan): Add coverage to make sure modifications to this function // doesn't cause cache invalidations (e.g., #130108). - fn configure_linker(&mut self, builder: &Builder<'_>) -> &mut Cargo { + fn configure_linker(&mut self, builder: &Builder<'_>, mode: Mode) -> &mut Cargo { let target = self.target; let compiler = self.compiler; @@ -264,7 +264,12 @@ impl Cargo { } } - for arg in linker_args(builder, compiler.host, LldThreads::Yes) { + // We use the snapshot compiler when building host code (build scripts/proc macros) of + // `Mode::Std` tools, so we need to determine the current stage here to pass the proper + // linker args (e.g. -C vs -Z). + // This should stay synchronized with the [cargo] function. + let host_stage = if mode == Mode::Std { 0 } else { compiler.stage }; + for arg in linker_args(builder, compiler.host, LldThreads::Yes, host_stage) { self.hostflags.arg(&arg); } @@ -274,10 +279,10 @@ impl Cargo { } // We want to set -Clinker using Cargo, therefore we only call `linker_flags` and not // `linker_args` here. - for flag in linker_flags(builder, target, LldThreads::Yes) { + for flag in linker_flags(builder, target, LldThreads::Yes, compiler.stage) { self.rustflags.arg(&flag); } - for arg in linker_args(builder, target, LldThreads::Yes) { + for arg in linker_args(builder, target, LldThreads::Yes, compiler.stage) { self.rustdocflags.arg(&arg); } diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index b96a988cde3..7464327fde9 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -1599,7 +1599,7 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s cmd.arg("-Dwarnings"); } cmd.arg("-Znormalize-docs"); - cmd.args(linker_args(self, compiler.host, LldThreads::Yes)); + cmd.args(linker_args(self, compiler.host, LldThreads::Yes, compiler.stage)); cmd } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 1d5690a8197..bbcb58fca14 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -758,6 +758,58 @@ mod snapshot { } #[test] + fn build_compiler_tools() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx + .config("build") + .stage(2) + .args(&["--set", "rust.lld=true", "--set", "rust.llvm-bitcode-linker=true"]) + .render_steps(), @r" + [build] llvm <host> + [build] rustc 0 <host> -> rustc 1 <host> + [build] rustc 0 <host> -> LldWrapper 1 <host> + [build] rustc 1 <host> -> LlvmBitcodeLinker 2 <host> + [build] rustc 1 <host> -> std 1 <host> + [build] rustc 1 <host> -> rustc 2 <host> + [build] rustc 1 <host> -> LldWrapper 2 <host> + [build] rustc 2 <host> -> LlvmBitcodeLinker 3 <host> + [build] rustc 2 <host> -> std 2 <host> + [build] rustdoc 1 <host> + " + ); + } + + #[test] + fn build_compiler_tools_cross() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx + .config("build") + .stage(2) + .args(&["--set", "rust.lld=true", "--set", "rust.llvm-bitcode-linker=true"]) + .hosts(&[TEST_TRIPLE_1]) + .render_steps(), @r" + [build] llvm <host> + [build] rustc 0 <host> -> rustc 1 <host> + [build] rustc 0 <host> -> LldWrapper 1 <host> + [build] rustc 1 <host> -> LlvmBitcodeLinker 2 <host> + [build] rustc 1 <host> -> std 1 <host> + [build] rustc 1 <host> -> rustc 2 <host> + [build] rustc 1 <host> -> LldWrapper 2 <host> + [build] rustc 2 <host> -> LlvmBitcodeLinker 3 <host> + [build] rustc 1 <host> -> std 1 <target1> + [build] rustc 2 <host> -> std 2 <target1> + [build] llvm <target1> + [build] rustc 1 <host> -> rustc 2 <target1> + [build] rustc 1 <host> -> LldWrapper 2 <target1> + [build] rustc 2 <target1> -> LlvmBitcodeLinker 3 <target1> + [build] rustdoc 1 <target1> + " + ); + } + + #[test] fn build_library_no_explicit_stage() { let ctx = TestCtx::new(); insta::assert_snapshot!( @@ -1040,6 +1092,7 @@ mod snapshot { [build] rustc 0 <host> -> cargo-clippy 1 <host> [build] rustc 0 <host> -> miri 1 <host> [build] rustc 0 <host> -> cargo-miri 1 <host> + [build] rustc 1 <host> -> LlvmBitcodeLinker 2 <host> "); } @@ -1230,6 +1283,7 @@ mod snapshot { [build] rustc 0 <host> -> cargo-clippy 1 <target1> [build] rustc 0 <host> -> miri 1 <target1> [build] rustc 0 <host> -> cargo-miri 1 <target1> + [build] rustc 1 <host> -> LlvmBitcodeLinker 2 <target1> "); } diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index bfc06f90d4f..19e0d3413bb 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -383,7 +383,10 @@ pub enum Subcommand { bless: bool, #[arg(long)] /// comma-separated list of other files types to check (accepts py, py:lint, - /// py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix) + /// py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck) + /// + /// Any argument can be prefixed with "auto:" to only run if + /// relevant files are modified (eg. "auto:py"). extra_checks: Option<String>, #[arg(long)] /// rerun tests even if the inputs are unchanged diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index ac5eaea3bcb..0fae235bb93 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -619,7 +619,6 @@ impl Config { // build our internal lld and use it as the default linker, by setting the `rust.lld` config // to true by default: // - on the `x86_64-unknown-linux-gnu` target - // - on the `dev` and `nightly` channels // - when building our in-tree llvm (i.e. the target has not set an `llvm-config`), so that // we're also able to build the corresponding lld // - or when using an external llvm that's downloaded from CI, which also contains our prebuilt @@ -628,9 +627,7 @@ impl Config { // thus, disabled // - similarly, lld will not be built nor used by default when explicitly asked not to, e.g. // when the config sets `rust.lld = false` - if self.host_target.triple == "x86_64-unknown-linux-gnu" - && self.hosts == [self.host_target] - && (self.channel == "dev" || self.channel == "nightly") + if self.host_target.triple == "x86_64-unknown-linux-gnu" && self.hosts == [self.host_target] { let no_llvm_config = self .target_config diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 424f211c7d4..29591edc9cc 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -461,4 +461,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Warning, summary: "`download-rustc` has been temporarily disabled for the library profile due to implementation bugs (see #142505).", }, + ChangeInfo { + change_id: 143398, + severity: ChangeSeverity::Info, + summary: "The --extra-checks flag now supports prefixing any check with `auto:` to only run it if relevant files are modified", + }, ]; diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index 3c5f612daa7..eb00ed566c2 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -404,8 +404,9 @@ pub fn linker_args( builder: &Builder<'_>, target: TargetSelection, lld_threads: LldThreads, + stage: u32, ) -> Vec<String> { - let mut args = linker_flags(builder, target, lld_threads); + let mut args = linker_flags(builder, target, lld_threads, stage); if let Some(linker) = builder.linker(target) { args.push(format!("-Clinker={}", linker.display())); @@ -420,19 +421,30 @@ pub fn linker_flags( builder: &Builder<'_>, target: TargetSelection, lld_threads: LldThreads, + stage: u32, ) -> Vec<String> { let mut args = vec![]; if !builder.is_lld_direct_linker(target) && builder.config.lld_mode.is_used() { match builder.config.lld_mode { LldMode::External => { - args.push("-Zlinker-features=+lld".to_string()); - // FIXME(kobzol): remove this flag once MCP510 gets stabilized + // cfg(bootstrap) - remove the stage 0 check after updating the bootstrap compiler: + // `-Clinker-features` has been stabilized. + if stage == 0 { + args.push("-Zlinker-features=+lld".to_string()); + } else { + args.push("-Clinker-features=+lld".to_string()); + } args.push("-Zunstable-options".to_string()); } LldMode::SelfContained => { - args.push("-Zlinker-features=+lld".to_string()); + // cfg(bootstrap) - remove the stage 0 check after updating the bootstrap compiler: + // `-Clinker-features` has been stabilized. + if stage == 0 { + args.push("-Zlinker-features=+lld".to_string()); + } else { + args.push("-Clinker-features=+lld".to_string()); + } args.push("-Clink-self-contained=+linker".to_string()); - // FIXME(kobzol): remove this flag once MCP510 gets stabilized args.push("-Zunstable-options".to_string()); } LldMode::Unused => unreachable!(), @@ -453,8 +465,9 @@ pub fn add_rustdoc_cargo_linker_args( builder: &Builder<'_>, target: TargetSelection, lld_threads: LldThreads, + stage: u32, ) { - let args = linker_args(builder, target, lld_threads); + let args = linker_args(builder, target, lld_threads, stage); let mut flags = cmd .get_envs() .find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None }) diff --git a/src/bootstrap/src/utils/tests/mod.rs b/src/bootstrap/src/utils/tests/mod.rs index 59c169b0f2b..5b568c1df5b 100644 --- a/src/bootstrap/src/utils/tests/mod.rs +++ b/src/bootstrap/src/utils/tests/mod.rs @@ -96,8 +96,6 @@ impl ConfigBuilder { // in-tree LLVM from sources. self.args.push("--set".to_string()); self.args.push("llvm.download-ci-llvm=false".to_string()); - self.args.push("--set".to_string()); - self.args.push(format!("target.'{}'.llvm-config=false", get_host_target())); // Do not mess with the local rustc checkout build directory self.args.push("--build-dir".to_string()); diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 9fe4c218121..e1742631f63 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -83,6 +83,7 @@ - [m68k-unknown-linux-gnu](platform-support/m68k-unknown-linux-gnu.md) - [m68k-unknown-none-elf](platform-support/m68k-unknown-none-elf.md) - [mips64-openwrt-linux-musl](platform-support/mips64-openwrt-linux-musl.md) + - [mips64-unknown-linux-muslabi64](platform-support/mips64-unknown-linux-muslabi64.md) - [mipsel-sony-psx](platform-support/mipsel-sony-psx.md) - [mipsel-unknown-linux-gnu](platform-support/mipsel-unknown-linux-gnu.md) - [mips\*-mti-none-elf](platform-support/mips-mti-none-elf.md) diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index bb109adf76f..07eafdf4c4c 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -235,15 +235,33 @@ coverage measurement. Its use is not recommended. ## link-self-contained -On `windows-gnu`, `linux-musl`, and `wasi` targets, this flag controls whether the -linker will use libraries and objects shipped with Rust instead of those in the system. -It takes one of the following values: +This flag controls whether the linker will use libraries and objects shipped with Rust instead of +those in the system. It also controls which binary is used for the linker itself. This allows +overriding cases when detection fails or the user wants to use shipped libraries. + +You can enable or disable the usage of any self-contained components using one of the following values: * no value: rustc will use heuristic to disable self-contained mode if system has necessary tools. * `y`, `yes`, `on`, `true`: use only libraries/objects shipped with Rust. * `n`, `no`, `off` or `false`: rely on the user or the linker to provide non-Rust libraries/objects. -This allows overriding cases when detection fails or user wants to use shipped libraries. +It is also possible to enable or disable specific self-contained components in a more granular way. +You can pass a comma-separated list of self-contained components, individually enabled +(`+component`) or disabled (`-component`). + +Currently, only the `linker` granular option is stabilized, and only on the `x86_64-unknown-linux-gnu` target: +- `linker`: toggle the usage of self-contained linker binaries (linker, dlltool, and their necessary libraries) + +Note that only the `-linker` opt-out is stable on the `x86_64-unknown-linux-gnu` target: `+linker` is +already the default on this target. + +#### Implementation notes + +On the `x86_64-unknown-linux-gnu` target, when using the default linker flavor (using `cc` as the +linker driver) and linker features (to try using `lld`), `rustc` will try to use the self-contained +linker by passing a `-B /path/to/sysroot/` link argument to the driver to find `rust-lld` in the +sysroot. For backwards-compatibility, and to limit name and `PATH` collisions, this is done using a +shim executable (the `lld-wrapper` tool) that forwards execution to the `rust-lld` executable itself. ## linker @@ -256,6 +274,39 @@ Note that on Unix-like targets (for example, `*-unknown-linux-gnu` or `*-unknown the C compiler (for example `cc` or `clang`) is used as the "linker" here, serving as a linker driver. It will invoke the actual linker with all the necessary flags to be able to link against the system libraries like libc. +## linker-features + +The `-Clinker-features` flag allows enabling or disabling specific features used during linking. + +These feature flags are a flexible extension mechanism that is complementary to linker flavors, +designed to avoid the combinatorial explosion of having to create a new set of flavors for each +linker feature we'd want to use. + +The flag accepts a comma-separated list of features, individually enabled (`+feature`) or disabled +(`-feature`). + +Currently only one is stable, and only on the `x86_64-unknown-linux-gnu` target: +- `lld`: to toggle trying to use the lld linker, either the system-installed binary, or the self-contained + `rust-lld` linker (via the [`-Clink-self-contained=+linker`](#link-self-contained) flag). + +For example, use: +- `-Clinker-features=+lld` to opt into using the `lld` linker, when possible (see the Implementation notes below) +- `-Clinker-features=-lld` to opt out instead, for targets where it is configured as the default linker + +Note that only the `-lld` opt-out is stable on the `x86_64-unknown-linux-gnu` target: `+lld` is +already the default on this target. + +#### Implementation notes + +On the `x86_64-unknown-linux-gnu` target, when using the default linker flavor (using `cc` as the +linker driver), `rustc` will try to use lld by passing a `-fuse-ld=lld` link argument to the driver. +`rustc` will also try to detect if that _causes_ an error during linking (for example, if GCC is too +old to understand the flag, and returns an error) and will then retry linking without this argument, +as a fallback. + +If the user _also_ passes a `-Clink-arg=-fuse-ld=$value`, both will be given to the linker +driver but the user's will be passed last, and would generally have priority over `rustc`'s. + ## linker-flavor This flag controls the linker flavor used by `rustc`. If a linker is given with diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 285b1e519b7..ed01de4c1c3 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -333,7 +333,7 @@ target | std | host | notes `mips-unknown-linux-uclibc` | ✓ | | MIPS Linux with uClibc [`mips64-openwrt-linux-musl`](platform-support/mips64-openwrt-linux-musl.md) | ? | | MIPS64 for OpenWrt Linux musl 1.2.3 `mips64-unknown-linux-gnuabi64` | ✓ | ✓ | MIPS64 Linux, N64 ABI (kernel 4.4, glibc 2.23) -`mips64-unknown-linux-muslabi64` | ✓ | | MIPS64 Linux, N64 ABI, musl 1.2.3 +[`mips64-unknown-linux-muslabi64`](platform-support/mips64-unknown-linux-muslabi64.md) | ✓ | ✓ | MIPS64 Linux, N64 ABI, musl 1.2.3 `mips64el-unknown-linux-gnuabi64` | ✓ | ✓ | MIPS64 (little endian) Linux, N64 ABI (kernel 4.4, glibc 2.23) `mips64el-unknown-linux-muslabi64` | ✓ | | MIPS64 (little endian) Linux, N64 ABI, musl 1.2.3 `mipsel-sony-psp` | * | | MIPS (LE) Sony PlayStation Portable (PSP) diff --git a/src/doc/rustc/src/platform-support/mips64-unknown-linux-muslabi64.md b/src/doc/rustc/src/platform-support/mips64-unknown-linux-muslabi64.md new file mode 100644 index 00000000000..60c0972bb3e --- /dev/null +++ b/src/doc/rustc/src/platform-support/mips64-unknown-linux-muslabi64.md @@ -0,0 +1,49 @@ +# mips64-unknown-linux-muslabi64 + +**Tier: 3** + +Target for 64-bit big endian MIPS Linux programs using musl libc and the N64 ABI. + +## Target maintainers + +[@Gelbpunkt](https://github.com/Gelbpunkt) + +## Requirements + +Building the target itself requires a 64-bit big endian MIPS compiler that is +supported by `cc-rs`. + +## Building the target + +The target can be built by enabling it for a `rustc` build. + +```toml +[build] +target = ["mips64-unknown-linux-muslabi64"] +``` + +Make sure your C compiler is included in `$PATH`, then add it to the +`bootstrap.toml`: + +```toml +[target.mips64-unknown-linux-muslabi64] +cc = "mips64-linux-musl-gcc" +cxx = "mips64-linux-musl-g++" +ar = "mips64-linux-musl-ar" +linker = "mips64-linux-musl-gcc" +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will first need to build Rust with the target enabled (see +"Building the target" above). + +## Cross-compilation + +This target can be cross-compiled from any host. + +## Testing + +This target can be tested as normal with `x.py` on a 64-bit big endian MIPS +host or via QEMU emulation. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasip1.md b/src/doc/rustc/src/platform-support/wasm32-wasip1.md index 4f065a554cf..a8a9e550581 100644 --- a/src/doc/rustc/src/platform-support/wasm32-wasip1.md +++ b/src/doc/rustc/src/platform-support/wasm32-wasip1.md @@ -4,37 +4,35 @@ The `wasm32-wasip1` target is a WebAssembly compilation target which assumes that the [WASIp1] (aka "WASI preview1") set of "syscalls" are available -for use in the standard library. Historically this target in the Rust compiler -was one of the first for WebAssembly where Rust and C code are explicitly -intended to interoperate as well. - -There's a bit of history to the target and current development which is also -worth explaining before going much further. Historically this target was -originally called `wasm32-wasi` in both rustc and Clang. This was first added -to Rust in 2019. In the intervening years leading up to 2024 the WASI standard -continued to be developed and was eventually "rebased" on top of the [Component -Model]. This was a large change to the WASI specification and was released as -0.2.0 ("WASIp2" colloquially) in January 2024. The previous target's name in -rustc, `wasm32-wasi`, was then renamed to `wasm32-wasip1`, to avoid -confusion with this new target to be added to rustc as `wasm32-wasip2`. -Some more context can be found in these MCPs: - -* [Rename wasm32-wasi target to wasm32-wasip1](https://github.com/rust-lang/compiler-team/issues/607) -* [Smooth the renaming transition of wasm32-wasi](https://github.com/rust-lang/compiler-team/issues/695) - -At this point the `wasm32-wasip1` target is intended for historical -compatibility with the first version of the WASI standard. As of now (January -2024) the 0.2.0 target of WASI ("WASIp2") is relatively new. The state of -WASI will likely change in few years after which point this documentation will -probably receive another update. - -[WASI Preview1]: https://github.com/WebAssembly/WASI/tree/main/legacy/preview1 +for use in the standard library. This target explicitly supports interop with +non-Rust code such as C and C++. + +The [WASIp1] set of syscalls is standard insofar as it was written down once by +a set of folks and has not changed since then. Additionally the [WASIp1] +syscalls have been adapted and adopted into a number of runtimes and embeddings. +It is not standard in the sense that there are no formal semantics for each +syscall and APIs are no longer receiving any maintenance (e.g. no new APIs, no +new documentation, etc). After [WASIp1] was originally developed in 2019 the +WASI standard effort has since been "rebased" on top of the [Component Model]. +This was a large change to the WASI specification and was released as 0.2.0 +("WASIp2" colloquially) in January 2024. Current standardization efforts are +focused on the Component Model-based definition of WASI. At this point the +`wasm32-wasip1` Rust target is intended for historical compatibility with +[WASIp1] set of syscalls. + +[WASIp1]: https://github.com/WebAssembly/WASI/tree/main/legacy/preview1 [Component Model]: https://github.com/webassembly/component-model Today the `wasm32-wasip1` target will generate core WebAssembly modules which will import functions from the `wasi_snapshot_preview1` module for OS-related functionality (e.g. printing). +> **Note**: Prior to March 2024 this target was known as `wasm32-wasi` with some +> historical context found in old MCPs: +> +> * [Rename wasm32-wasi target to wasm32-wasip1](https://github.com/rust-lang/compiler-team/issues/607) +> * [Smooth the renaming transition of wasm32-wasi](https://github.com/rust-lang/compiler-team/issues/695) + ## Target maintainers When this target was added to the compiler platform-specific documentation here @@ -44,6 +42,7 @@ said since when this document was last updated those interested in maintaining this target are: [@alexcrichton](https://github.com/alexcrichton) +[@loganek](https://github.com/loganek) ## Requirements diff --git a/src/doc/unstable-book/src/compiler-flags/codegen-options.md b/src/doc/unstable-book/src/compiler-flags/codegen-options.md index cc51554706d..f927e5c439c 100644 --- a/src/doc/unstable-book/src/compiler-flags/codegen-options.md +++ b/src/doc/unstable-book/src/compiler-flags/codegen-options.md @@ -51,10 +51,10 @@ instead of those in the system. The stable boolean values for this flag are coar - `mingw`: other MinGW libs and Windows import libs Out of the above self-contained linking components, `linker` is the only one currently implemented -(beyond parsing the CLI options). +(beyond parsing the CLI options) and stabilized. It refers to the LLD linker, built from the same LLVM revision used by rustc (named `rust-lld` to avoid naming conflicts), that is distributed via `rustup` with the compiler (and is used by default -for the wasm targets). One can also opt-in to use it by combining this flag with an appropriate -linker flavor: for example, `-Clinker-flavor=gnu-lld-cc -Clink-self-contained=+linker` will use the -toolchain's `rust-lld` as the linker. +for the wasm targets). One can also opt into using it by combining this flag with the appropriate +linker feature: for example, `-Clinker-features=+lld -Clink-self-contained=+linker` will use the +toolchain's `rust-lld` as the linker instead of the system's lld with `-Clinker-features=+lld` only. diff --git a/src/doc/unstable-book/src/compiler-flags/linker-features.md b/src/doc/unstable-book/src/compiler-flags/linker-features.md deleted file mode 100644 index 643fcf7c6d7..00000000000 --- a/src/doc/unstable-book/src/compiler-flags/linker-features.md +++ /dev/null @@ -1,35 +0,0 @@ -# `linker-features` - --------------------- - -The `-Zlinker-features` compiler flag allows enabling or disabling specific features used during -linking, and is intended to be stabilized under the codegen options as `-Clinker-features`. - -These feature flags are a flexible extension mechanism that is complementary to linker flavors, -designed to avoid the combinatorial explosion of having to create a new set of flavors for each -linker feature we'd want to use. - -For example, this design allows: -- default feature sets for principal flavors, or for specific targets. -- flavor-specific features: for example, clang offers automatic cross-linking with `--target`, which - gcc-style compilers don't support. The *flavor* is still a C/C++ compiler, and we don't want to - multiply the number of flavors for this use-case. Instead, we can have a single `+target` feature. -- umbrella features: for example, if clang accumulates more features in the future than just the - `+target` above. That could be modeled as `+clang`. -- niche features for resolving specific issues: for example, on Apple targets the linker flag - implementing the `as-needed` native link modifier (#99424) is only possible on sufficiently recent - linker versions. -- still allows for discovery and automation, for example via feature detection. This can be useful - in exotic environments/build systems. - -The flag accepts a comma-separated list of features, individually enabled (`+features`) or disabled -(`-features`), though currently only one is exposed on the CLI: -- `lld`: to toggle using the lld linker, either the system-installed binary, or the self-contained - `rust-lld` linker. - -As described above, this list is intended to grow in the future. - -One of the most common uses of this flag will be to toggle self-contained linking with `rust-lld` on -and off: `-Clinker-features=+lld -Clink-self-contained=+linker` will use the toolchain's `rust-lld` -as the linker. Inversely, `-Clinker-features=-lld` would opt out of that, if the current target had -self-contained linking enabled by default. diff --git a/src/etc/completions/x.fish b/src/etc/completions/x.fish index 69bd525a312..3009dd85e93 100644 --- a/src/etc/completions/x.fish +++ b/src/etc/completions/x.fish @@ -293,7 +293,7 @@ complete -c x -n "__fish_x_using_subcommand doc" -l skip-std-check-if-no-downloa complete -c x -n "__fish_x_using_subcommand doc" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x -n "__fish_x_using_subcommand test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r complete -c x -n "__fish_x_using_subcommand test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r -complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)' -r +complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)' -r complete -c x -n "__fish_x_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r complete -c x -n "__fish_x_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r complete -c x -n "__fish_x_using_subcommand test" -l run -d 'whether to execute run-* tests' -r diff --git a/src/etc/completions/x.ps1 b/src/etc/completions/x.ps1 index 7b142ba97ce..8002ec31592 100644 --- a/src/etc/completions/x.ps1 +++ b/src/etc/completions/x.ps1 @@ -339,7 +339,7 @@ Register-ArgumentCompleter -Native -CommandName 'x' -ScriptBlock { 'x;test' { [CompletionResult]::new('--test-args', '--test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)') [CompletionResult]::new('--compiletest-rustc-args', '--compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests') - [CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)') + [CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)') [CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to') [CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode') [CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests') diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish index e982cde634f..4766a5ee0c7 100644 --- a/src/etc/completions/x.py.fish +++ b/src/etc/completions/x.py.fish @@ -293,7 +293,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand doc" -l skip-std-check-if-no-d complete -c x.py -n "__fish_x.py_using_subcommand doc" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_x.py_using_subcommand test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r complete -c x.py -n "__fish_x.py_using_subcommand test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r -complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)' -r +complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)' -r complete -c x.py -n "__fish_x.py_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r complete -c x.py -n "__fish_x.py_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r complete -c x.py -n "__fish_x.py_using_subcommand test" -l run -d 'whether to execute run-* tests' -r diff --git a/src/etc/completions/x.py.ps1 b/src/etc/completions/x.py.ps1 index f4b26fac0bc..1aff3c433b4 100644 --- a/src/etc/completions/x.py.ps1 +++ b/src/etc/completions/x.py.ps1 @@ -339,7 +339,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { 'x.py;test' { [CompletionResult]::new('--test-args', '--test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)') [CompletionResult]::new('--compiletest-rustc-args', '--compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests') - [CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)') + [CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck)') [CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to') [CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode') [CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests') diff --git a/src/etc/completions/x.py.zsh b/src/etc/completions/x.py.zsh index 8fc4f052252..77f995c1704 100644 --- a/src/etc/completions/x.py.zsh +++ b/src/etc/completions/x.py.zsh @@ -338,7 +338,7 @@ _arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \ '*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS:_default' \ '*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS:_default' \ -'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck, spellcheck\:fix)]:EXTRA_CHECKS:_default' \ +'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck)]:EXTRA_CHECKS:_default' \ '--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \ '--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \ '--run=[whether to execute run-* tests]:auto | always | never:_default' \ diff --git a/src/etc/completions/x.zsh b/src/etc/completions/x.zsh index c495e8318ba..6c6d7b3f49f 100644 --- a/src/etc/completions/x.zsh +++ b/src/etc/completions/x.zsh @@ -338,7 +338,7 @@ _arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \ '*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS:_default' \ '*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS:_default' \ -'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck, spellcheck\:fix)]:EXTRA_CHECKS:_default' \ +'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, shell\:lint, cpp, cpp\:fmt, spellcheck)]:EXTRA_CHECKS:_default' \ '--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \ '--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \ '--run=[whether to execute run-* tests]:auto | always | never:_default' \ diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 41c3f03b91b..0078671fcc5 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -25,7 +25,6 @@ use std::str::FromStr; use std::{fmt, fs}; use indexmap::IndexMap; -use itertools::Itertools; use regex::Regex; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; @@ -44,6 +43,7 @@ use crate::docfs::PathError; use crate::error::Error; use crate::formats::Impl; use crate::formats::item_type::ItemType; +use crate::html::format::join_with_double_colon; use crate::html::layout; use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::search_index::{SerializedSearchIndex, build_index}; @@ -608,7 +608,7 @@ impl TypeAliasPart { for &(type_alias_fqp, type_alias_item) in type_aliases { cx.id_map.borrow_mut().clear(); cx.deref_id_map.borrow_mut().clear(); - let type_alias_fqp = (*type_alias_fqp).iter().join("::"); + let type_alias_fqp = join_with_double_colon(&type_alias_fqp); if let Some(ret) = &mut ret { ret.aliases.push(type_alias_fqp); } else { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index abf3f3fcedd..e7163bead92 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -7,6 +7,7 @@ use rustc_ast::ast; use rustc_attr_data_structures::{self as attrs, DeprecatedSince}; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; +use rustc_hir::{HeaderSafety, Safety}; use rustc_metadata::rendered_const; use rustc_middle::{bug, ty}; use rustc_span::{Pos, kw, sym}; @@ -381,10 +382,22 @@ impl FromClean<clean::Union> for Union { impl FromClean<rustc_hir::FnHeader> for FunctionHeader { fn from_clean(header: &rustc_hir::FnHeader, renderer: &JsonRenderer<'_>) -> Self { + let is_unsafe = match header.safety { + HeaderSafety::SafeTargetFeatures => { + // The type system's internal implementation details consider + // safe functions with the `#[target_feature]` attribute to be analogous + // to unsafe functions: `header.is_unsafe()` returns `true` for them. + // For rustdoc, this isn't the right decision, so we explicitly return `false`. + // Context: https://github.com/rust-lang/rust/issues/142655 + false + } + HeaderSafety::Normal(Safety::Safe) => false, + HeaderSafety::Normal(Safety::Unsafe) => true, + }; FunctionHeader { is_async: header.is_async(), is_const: header.is_const(), - is_unsafe: header.is_unsafe(), + is_unsafe, abi: header.abi.into_json(renderer), } } diff --git a/src/llvm-project b/src/llvm-project -Subproject 9b1bf4cf041c1c1fe62cf03891ac90431615e78 +Subproject 99f0e0531688a822a753cc585b7408b069cb682 diff --git a/src/tools/clippy/.github/workflows/feature_freeze.yml b/src/tools/clippy/.github/workflows/feature_freeze.yml index a5f8d4bc145..7ad58af77d4 100644 --- a/src/tools/clippy/.github/workflows/feature_freeze.yml +++ b/src/tools/clippy/.github/workflows/feature_freeze.yml @@ -1,7 +1,11 @@ name: Feature freeze check on: - pull_request: + pull_request_target: + types: + - opened + branches: + - master paths: - 'clippy_lints/src/declared_lints.rs' @@ -9,6 +13,12 @@ jobs: auto-comment: runs-on: ubuntu-latest + permissions: + pull-requests: write + + # Do not in any case add code that runs anything coming from the the content + # of the pull request, as malicious code would be able to access the private + # GitHub token. steps: - name: Check PR Changes id: pr-changes @@ -19,7 +29,7 @@ jobs: run: | # Use GitHub API to create a comment on the PR PR_NUMBER=${{ github.event.pull_request.number }} - COMMENT="**Seems that you are trying to add a new lint!**\nWe are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to August 1st and focusing on bugfixes.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team" + COMMENT="**Seems that you are trying to add a new lint!**\nWe are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and focusing on bugfixes.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team\n\n@rustbot note Feature-freeze\n@rustbot blocked\n@rustbot label +A-lint\n" GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}" diff --git a/src/tools/clippy/.github/workflows/lintcheck.yml b/src/tools/clippy/.github/workflows/lintcheck.yml index 70c805903d3..003d0395739 100644 --- a/src/tools/clippy/.github/workflows/lintcheck.yml +++ b/src/tools/clippy/.github/workflows/lintcheck.yml @@ -128,21 +128,27 @@ jobs: - name: Download JSON uses: actions/download-artifact@v4 + - name: Store PR number + run: echo ${{ github.event.pull_request.number }} > pr.txt + - name: Diff results - # GH's summery has a maximum size of 1024k: - # https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary - # That's why we first log to file and then to the summary and logs + # GH's summery has a maximum size of 1MiB: + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#step-isolation-and-limits + # We upload the full diff as an artifact in case it's truncated run: | - ./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --truncate >> truncated_diff.md - head -c 1024000 truncated_diff.md >> $GITHUB_STEP_SUMMARY - cat truncated_diff.md - ./target/debug/lintcheck diff {base,head}/ci_crates_logs.json >> full_diff.md + ./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --truncate | head -c 1M > $GITHUB_STEP_SUMMARY + ./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --write-summary summary.json > full_diff.md - name: Upload full diff uses: actions/upload-artifact@v4 with: - name: diff - if-no-files-found: ignore + name: full_diff + path: full_diff.md + + - name: Upload summary + uses: actions/upload-artifact@v4 + with: + name: summary path: | - full_diff.md - truncated_diff.md + summary.json + pr.txt diff --git a/src/tools/clippy/.github/workflows/lintcheck_summary.yml b/src/tools/clippy/.github/workflows/lintcheck_summary.yml new file mode 100644 index 00000000000..52f52e155a0 --- /dev/null +++ b/src/tools/clippy/.github/workflows/lintcheck_summary.yml @@ -0,0 +1,106 @@ +name: Lintcheck summary + +# The workflow_run event runs in the context of the Clippy repo giving it write +# access, needed here to create a PR comment when the PR originates from a fork +# +# The summary artifact is a JSON file that we verify in this action to prevent +# the creation of arbitrary comments +# +# This action must not checkout/run code from the originating workflow_run +# or directly interpolate ${{}} untrusted fields into code +# +# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run +# https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections + +on: + workflow_run: + workflows: [Lintcheck] + types: [completed] + +# Restrict the default permission scope https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#defining-access-for-the-github_token-scopes +permissions: + pull-requests: write + +jobs: + download: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: summary + path: untrusted + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + + - name: Format comment + uses: actions/github-script@v7 + with: + script: | + const fs = require("fs"); + const assert = require("assert/strict"); + + function validateName(s) { + assert.match(s, /^[a-z0-9_:]+$/); + return s; + } + + function validateInt(i) { + assert.ok(Number.isInteger(i)); + return i; + } + + function tryReadSummary() { + try { + return JSON.parse(fs.readFileSync("untrusted/summary.json")); + } catch { + return null; + } + } + + const prNumber = parseInt(fs.readFileSync("untrusted/pr.txt"), 10); + core.exportVariable("PR", prNumber.toString()); + + const untrustedSummary = tryReadSummary(); + if (!Array.isArray(untrustedSummary)) { + return; + } + + let summary = `Lintcheck changes for ${context.payload.workflow_run.head_sha} + + | Lint | Added | Removed | Changed | + | ---- | ----: | ------: | ------: | + `; + + for (const untrustedRow of untrustedSummary) { + const name = validateName(untrustedRow.name); + + const added = validateInt(untrustedRow.added); + const removed = validateInt(untrustedRow.removed); + const changed = validateInt(untrustedRow.changed); + + const id = name.replace("clippy::", "user-content-").replaceAll("_", "-"); + const url = `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${context.payload.workflow_run.id}#${id}`; + + summary += `| [\`${name}\`](${url}) | ${added} | ${removed} | ${changed} |\n`; + } + + summary += "\nThis comment will be updated if you push new changes"; + + fs.writeFileSync("summary.md", summary); + + - name: Create/update comment + run: | + if [[ -f summary.md ]]; then + gh pr comment "$PR" --body-file summary.md --edit-last --create-if-none + else + # There were no changes detected by Lintcheck + # - If a comment exists from a previous run that did detect changes, edit it (--edit-last) + # - If no comment exists do not create one, the `gh` command exits with an error which + # `|| true` ignores + gh pr comment "$PR" --body "No changes for ${{ github.event.workflow_run.head_sha }}" --edit-last || true + fi + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml index 1278427b5a7..f25f9ab54dd 100644 --- a/src/tools/clippy/Cargo.toml +++ b/src/tools/clippy/Cargo.toml @@ -28,13 +28,13 @@ declare_clippy_lint = { path = "declare_clippy_lint" } rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } clippy_lints_internal = { path = "clippy_lints_internal", optional = true } tempfile = { version = "3.20", optional = true } -termize = "0.1" +termize = "0.2" color-print = "0.3.4" anstream = "0.6.18" [dev-dependencies] cargo_metadata = "0.18.1" -ui_test = "0.29.2" +ui_test = "0.30.2" regex = "1.5.5" serde = { version = "1.0.145", features = ["derive"] } serde_json = "1.0.122" @@ -45,14 +45,6 @@ itertools = "0.12" pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] } -# UI test dependencies -if_chain = "1.0" -quote = "1.0.25" -syn = { version = "2.0", features = ["full"] } -futures = "0.3" -parking_lot = "0.12" -tokio = { version = "1", features = ["io-util"] } - [build-dependencies] rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } diff --git a/src/tools/clippy/book/src/development/infrastructure/backport.md b/src/tools/clippy/book/src/development/infrastructure/backport.md index 9526d8af1c9..47ea6a412c5 100644 --- a/src/tools/clippy/book/src/development/infrastructure/backport.md +++ b/src/tools/clippy/book/src/development/infrastructure/backport.md @@ -109,4 +109,4 @@ worth backporting this. When a PR is backported to Rust `beta`, label the PR with `beta-accepted`. This will then get picked up when [writing the changelog]. -[writing the changelog]: changelog_update.md#31-include-beta-accepted-prs +[writing the changelog]: changelog_update.md#4-include-beta-accepted-prs diff --git a/src/tools/clippy/book/src/development/infrastructure/changelog_update.md b/src/tools/clippy/book/src/development/infrastructure/changelog_update.md index eede6b78d92..c96ff228b01 100644 --- a/src/tools/clippy/book/src/development/infrastructure/changelog_update.md +++ b/src/tools/clippy/book/src/development/infrastructure/changelog_update.md @@ -38,7 +38,7 @@ Usually you want to write the changelog of the **upcoming stable release**. Make sure though, that `beta` was already branched in the Rust repository. To find the commit hash, issue the following command when in a `rust-lang/rust` -checkout: +checkout (most of the time on the `upstream/beta` branch): ``` git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g" ``` @@ -48,16 +48,13 @@ git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into Once you've got the correct commit range, run ``` -util/fetch_prs_between.sh commit1 commit2 > changes.txt +util/fetch_prs_between.sh start_commit end_commit > changes.txt ``` -where `commit2` is the commit hash from the previous command and `commit1` -is the commit hash from the current CHANGELOG file. +where `end_commit` is the commit hash from the previous command and `start_commit` +is [the commit hash][beta_section] from the current CHANGELOG file. Open `changes.txt` file in your editor of choice. -When updating the changelog it's also a good idea to make sure that `commit1` is -already correct in the current changelog. - ### 3. Authoring the final changelog The above script should have dumped all the relevant PRs to the file you @@ -70,17 +67,7 @@ With the PRs filtered, you can start to take each PR and move the `changelog: ` content to `CHANGELOG.md`. Adapt the wording as you see fit but try to keep it somewhat coherent. -The order should roughly be: - -1. New lints -2. Moves or deprecations of lints -3. Changes that expand what code existing lints cover -4. False positive fixes -5. ICE fixes -6. Documentation improvements -7. Others - -As section headers, we use: +The sections order should roughly be: ``` ### New Lints @@ -97,10 +84,10 @@ As section headers, we use: ### Others ``` -Please also be sure to update the Beta/Unreleased sections at the top with the -relevant commit ranges. +Please also be sure to update [the `Unreleased/Beta/In Rust Nightly` section][beta_section] at the top with the +relevant commits ranges and to add the `Rust <version>` section with release date and PR ranges. -#### 3.1 Include `beta-accepted` PRs +### 4. Include `beta-accepted` PRs Look for the [`beta-accepted`] label and make sure to also include the PRs with that label in the changelog. If you can, remove the `beta-accepted` labels @@ -109,7 +96,7 @@ that label in the changelog. If you can, remove the `beta-accepted` labels > _Note:_ Some of those PRs might even get backported to the previous `beta`. > Those have to be included in the changelog of the _previous_ release. -### 4. Update `clippy::version` attributes +### 5. Update `clippy::version` attributes Next, make sure to check that the `#[clippy::version]` attributes for the added lints contain the correct version. @@ -129,3 +116,4 @@ written for. If not, update the version to the changelog version. [rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy [rust_stable_tools]: https://github.com/rust-lang/rust/releases [`beta-accepted`]: https://github.com/rust-lang/rust-clippy/issues?q=label%3Abeta-accepted+ +[beta_section]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md#unreleased--beta--in-rust-nightly diff --git a/src/tools/clippy/book/src/lint_configuration.md b/src/tools/clippy/book/src/lint_configuration.md index e9b7f42a183..992ed2c6aaa 100644 --- a/src/tools/clippy/book/src/lint_configuration.md +++ b/src/tools/clippy/book/src/lint_configuration.md @@ -892,6 +892,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns) * [`unused_trait_names`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names) * [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self) +* [`zero_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr) ## `pass-by-value-size-limit` diff --git a/src/tools/clippy/clippy_config/src/conf.rs b/src/tools/clippy/clippy_config/src/conf.rs index 841facdca06..555f54bcfb8 100644 --- a/src/tools/clippy/clippy_config/src/conf.rs +++ b/src/tools/clippy/clippy_config/src/conf.rs @@ -794,6 +794,7 @@ define_Conf! { unnested_or_patterns, unused_trait_names, use_self, + zero_ptr, )] msrv: Msrv = Msrv::default(), /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. diff --git a/src/tools/clippy/clippy_dev/src/fmt.rs b/src/tools/clippy/clippy_dev/src/fmt.rs index c1b6b370706..bd9e57c9f6d 100644 --- a/src/tools/clippy/clippy_dev/src/fmt.rs +++ b/src/tools/clippy/clippy_dev/src/fmt.rs @@ -223,7 +223,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> { if check { return Err(Error::CheckFailed); } - fs::write(path, new_text.as_bytes())?; + fs::write(path, new_text)?; } Ok(()) } diff --git a/src/tools/clippy/clippy_dev/src/lib.rs b/src/tools/clippy/clippy_dev/src/lib.rs index 3361443196a..40aadf4589a 100644 --- a/src/tools/clippy/clippy_dev/src/lib.rs +++ b/src/tools/clippy/clippy_dev/src/lib.rs @@ -2,7 +2,6 @@ rustc_private, exit_status_error, if_let_guard, - let_chains, os_str_slice, os_string_truncate, slice_split_once diff --git a/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs b/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs index 8b6bfaebbe5..c410a5da775 100644 --- a/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -266,7 +266,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { .tcx .hir_attrs(item.hir_id()) .iter() - .any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr{ .. }))) + .any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr { .. }))) { // Do not lint items with a `#[repr]` attribute as their layout may be imposed by an external API. return; diff --git a/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs index ad0a4f8cdf3..e3b125a8d5b 100644 --- a/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/casts/borrow_as_ptr.rs @@ -18,7 +18,8 @@ pub(super) fn check<'tcx>( cast_to: &'tcx Ty<'_>, msrv: Msrv, ) -> bool { - if matches!(cast_to.kind, TyKind::Ptr(_)) + if let TyKind::Ptr(target) = cast_to.kind + && !matches!(target.ty.kind, TyKind::TraitObject(..)) && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind && !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id) { diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs index a2ecb5fb44a..2eebe849232 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; -use clippy_utils::{expr_or_init, sym}; +use clippy_utils::{expr_or_init, is_in_const_context, sym}; use rustc_abi::IntegerType; use rustc_errors::{Applicability, Diag}; use rustc_hir::def::{DefKind, Res}; @@ -168,7 +168,9 @@ pub(super) fn check( span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| { diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ..."); - if !cast_from.is_floating_point() { + // TODO: Remove the condition for const contexts when `try_from` and other commonly used methods + // become const fn. + if !is_in_const_context(cx) && !cast_from.is_floating_point() { offer_suggestion(cx, expr, cast_expr, cast_to_span, diag); } }); diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs index daae9a8bb08..37accff5eaa 100644 --- a/src/tools/clippy/clippy_lints/src/casts/mod.rs +++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs @@ -878,7 +878,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to); - zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir); + zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv); if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) { manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir); diff --git a/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs index a34af6bc226..f4738e7b0d5 100644 --- a/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; use clippy_utils::{is_in_const_context, is_integer_literal, std_or_core}; use rustc_errors::Applicability; @@ -7,10 +8,10 @@ use rustc_lint::LateContext; use super::ZERO_PTR; -pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) { +pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>, msrv: Msrv) { if let TyKind::Ptr(ref mut_ty) = to.kind && is_integer_literal(from, 0) - && !is_in_const_context(cx) + && (!is_in_const_context(cx) || msrv.meets(cx, msrvs::PTR_NULL)) && let Some(std_or_core) = std_or_core(cx) { let (msg, sugg_fn) = match mut_ty.mutbl { diff --git a/src/tools/clippy/clippy_lints/src/coerce_container_to_any.rs b/src/tools/clippy/clippy_lints/src/coerce_container_to_any.rs index 2b659253763..6217fc4c897 100644 --- a/src/tools/clippy/clippy_lints/src/coerce_container_to_any.rs +++ b/src/tools/clippy/clippy_lints/src/coerce_container_to_any.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; +use clippy_utils::sugg::{self, Sugg}; use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::adjustment::{Adjust, PointerCoercion}; use rustc_middle::ty::{self, ExistentialPredicate, Ty, TyCtxt}; use rustc_session::declare_lint_pass; @@ -49,23 +50,18 @@ declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - // If this expression has an effective type of `&dyn Any` ... - { - let coerced_ty = cx.typeck_results().expr_ty_adjusted(e); - - let ty::Ref(_, coerced_ref_ty, _) = *coerced_ty.kind() else { - return; - }; - if !is_dyn_any(cx.tcx, coerced_ref_ty) { - return; - } + // If this expression was coerced to `&dyn Any` ... + if !cx.typeck_results().expr_adjustments(e).last().is_some_and(|adj| { + matches!(adj.kind, Adjust::Pointer(PointerCoercion::Unsize)) && is_ref_dyn_any(cx.tcx, adj.target) + }) { + return; } let expr_ty = cx.typeck_results().expr_ty(e); let ty::Ref(_, expr_ref_ty, _) = *expr_ty.kind() else { return; }; - // ... but only due to coercion ... + // ... but it's not actually `&dyn Any` ... if is_dyn_any(cx.tcx, expr_ref_ty) { return; } @@ -78,23 +74,37 @@ impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { } // ... that's probably not intended. - let (span, deref_count) = match e.kind { + let (target_expr, deref_count) = match e.kind { // If `e` was already an `&` expression, skip `*&` in the suggestion - ExprKind::AddrOf(_, _, referent) => (referent.span, depth), - _ => (e.span, depth + 1), + ExprKind::AddrOf(_, _, referent) => (referent, depth), + _ => (e, depth + 1), }; + let ty::Ref(_, _, mutability) = *cx.typeck_results().expr_ty_adjusted(e).kind() else { + return; + }; + let sugg = sugg::make_unop( + &format!("{}{}", mutability.ref_prefix_str(), str::repeat("*", deref_count)), + Sugg::hir(cx, target_expr, ".."), + ); span_lint_and_sugg( cx, COERCE_CONTAINER_TO_ANY, e.span, - format!("coercing `{expr_ty}` to `&dyn Any`"), + format!("coercing `{expr_ty}` to `{}dyn Any`", mutability.ref_prefix_str()), "consider dereferencing", - format!("&{}{}", str::repeat("*", deref_count), snippet(cx, span, "x")), + sugg.to_string(), Applicability::MaybeIncorrect, ); } } +fn is_ref_dyn_any(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { + let ty::Ref(_, ref_ty, _) = *ty.kind() else { + return false; + }; + is_dyn_any(tcx, ref_ty) +} + fn is_dyn_any(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { let ty::Dynamic(traits, ..) = ty.kind() else { return false; diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs index 5c64216dd92..d5d937d9133 100644 --- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -14,18 +14,25 @@ use rustc_span::def_id::LocalDefId; declare_clippy_lint! { /// ### What it does - /// Checks for methods with high cognitive complexity. + /// We used to think it measured how hard a method is to understand. /// /// ### Why is this bad? - /// Methods of high cognitive complexity tend to be hard to - /// both read and maintain. Also LLVM will tend to optimize small methods better. + /// Ideally, we would like to be able to measure how hard a function is + /// to understand given its context (what we call its Cognitive Complexity). + /// But that's not what this lint does. See "Known problems" /// /// ### Known problems - /// Sometimes it's hard to find a way to reduce the - /// complexity. + /// 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 + /// measurement tool. /// - /// ### Example - /// You'll see it when you get the warning. + /// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793) + /// + /// ### Lints to consider instead of this + /// + /// * [`excessive_nesting`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting) + /// * [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines) #[clippy::version = "1.35.0"] pub COGNITIVE_COMPLEXITY, nursery, diff --git a/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs b/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs index d1a8590c59b..cf964d4b580 100644 --- a/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs +++ b/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs @@ -89,6 +89,10 @@ impl EarlyLintPass for DisallowedScriptIdents { // Fast path for ascii-only idents. if !symbol_str.is_ascii() && let Some(script) = symbol_str.chars().find_map(|c| { + if c.is_ascii() { + return None; + } + c.script_extension() .iter() .find(|script| !self.whitelist.contains(script)) diff --git a/src/tools/clippy/clippy_lints/src/doc/missing_headers.rs b/src/tools/clippy/clippy_lints/src/doc/missing_headers.rs index 9ee32fced8c..3033ac0d0b0 100644 --- a/src/tools/clippy/clippy_lints/src/doc/missing_headers.rs +++ b/src/tools/clippy/clippy_lints/src/doc/missing_headers.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item}; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty}; +use clippy_utils::{fulfill_or_allowed, is_doc_hidden, is_inside_always_const_context, method_chain_args, return_ty}; use rustc_hir::{BodyId, FnSig, OwnerId, Safety}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -99,13 +99,16 @@ fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> { let mut panic_span = None; let typeck = cx.tcx.typeck_body(body_id); for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| { + if is_inside_always_const_context(cx.tcx, expr.hir_id) { + return ControlFlow::<!>::Continue(()); + } + if let Some(macro_call) = root_macro_call_first_node(cx, expr) && (is_panic(cx, macro_call.def_id) || matches!( cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro) )) - && !cx.tcx.hir_is_inside_const_context(expr.hir_id) && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id]) && panic_span.is_none() { diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index 5ea55e102df..2bf52216b83 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -1249,7 +1249,9 @@ fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> { b'[' => { start = Some(i + offset); }, - b']' if let Some(start) = start => { + b']' if let Some(start) = start + && doc.as_bytes().get(i + offset + 1) == Some(&b':') => + { return Some(start..i + offset + 1); }, _ => {}, diff --git a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs index 7dda3e0fdb9..8ad09279071 100644 --- a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs +++ b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs @@ -1,12 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::indent_of; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_attr_data_structures::AttributeKind; -use rustc_attr_data_structures::find_attr; - declare_clippy_lint! { /// ### What it does diff --git a/src/tools/clippy/clippy_lints/src/exit.rs b/src/tools/clippy/clippy_lints/src/exit.rs index cc8e4d7d9e2..487db69027a 100644 --- a/src/tools/clippy/clippy_lints/src/exit.rs +++ b/src/tools/clippy/clippy_lints/src/exit.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_entrypoint_fn; use rustc_hir::{Expr, ExprKind, Item, ItemKind, OwnerNode}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -7,7 +6,8 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Detects calls to the `exit()` function which terminates the program. + /// Detects calls to the `exit()` function that are not in the `main` function. Calls to `exit()` + /// immediately terminate the program. /// /// ### Why restrict this? /// `exit()` immediately terminates the program with no information other than an exit code. @@ -15,11 +15,24 @@ declare_clippy_lint! { /// /// Codebases may use this lint to require that all exits are performed either by panicking /// (which produces a message, a code location, and optionally a backtrace) - /// or by returning from `main()` (which is a single place to look). + /// or by calling `exit()` from `main()` (which is a single place to look). /// - /// ### Example + /// ### Good example /// ```no_run - /// std::process::exit(0) + /// fn main() { + /// std::process::exit(0); + /// } + /// ``` + /// + /// ### Bad example + /// ```no_run + /// fn main() { + /// other_function(); + /// } + /// + /// fn other_function() { + /// std::process::exit(0); + /// } /// ``` /// /// Use instead: @@ -36,7 +49,7 @@ declare_clippy_lint! { #[clippy::version = "1.41.0"] pub EXIT, restriction, - "detects `std::process::exit` calls" + "detects `std::process::exit` calls outside of `main`" } declare_lint_pass!(Exit => [EXIT]); @@ -48,10 +61,14 @@ impl<'tcx> LateLintPass<'tcx> for Exit { && let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::process_exit, def_id) && let parent = cx.tcx.hir_get_parent_item(e.hir_id) - && let OwnerNode::Item(Item{kind: ItemKind::Fn{ .. }, ..}) = cx.tcx.hir_owner_node(parent) - // If the next item up is a function we check if it is an entry point + && let OwnerNode::Item(Item{kind: ItemKind::Fn{ ident, .. }, ..}) = cx.tcx.hir_owner_node(parent) + // If the next item up is a function we check if it isn't named "main" // and only then emit a linter warning - && !is_entrypoint_fn(cx, parent.to_def_id()) + + // if you instead check for the parent of the `exit()` call being the entrypoint function, as this worked before, + // in compilation contexts like --all-targets (which include --tests), you get false positives + // because in a test context, main is not the entrypoint function + && ident.name != sym::main { span_lint(cx, EXIT, e.span, "usage of `process::exit`"); } diff --git a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs index b3c9e860758..d5abaa547e8 100644 --- a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs +++ b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs @@ -2,8 +2,8 @@ use clippy_utils::consts::Constant::{F32, F64, Int}; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::{ - eq_expr_value, get_parent_expr, higher, is_in_const_context, is_inherent_method_call, is_no_std_crate, - numeric_literal, peel_blocks, sugg, sym, + eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, + is_inherent_method_call, is_no_std_crate, numeric_literal, peel_blocks, sugg, sym, }; use rustc_ast::ast; use rustc_errors::Applicability; @@ -455,7 +455,6 @@ fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&' None } -// TODO: Fix rust-lang/rust-clippy#4735 fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::Binary( Spanned { @@ -491,6 +490,14 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { return; }; + // Check if any variable in the expression has an ambiguous type (could be f32 or f64) + // see: https://github.com/rust-lang/rust-clippy/issues/14897 + if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _))) + && has_ambiguous_literal_in_expr(cx, recv) + { + return; + } + span_lint_and_sugg( cx, SUBOPTIMAL_FLOPS, diff --git a/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs b/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs index 823cf0f4322..e809987d75a 100644 --- a/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/empty_loop.rs @@ -1,11 +1,22 @@ use super::EMPTY_LOOP; use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{is_in_panic_handler, is_no_std_crate}; +use clippy_utils::{is_in_panic_handler, is_no_std_crate, sym}; -use rustc_hir::{Block, Expr}; +use rustc_hir::{Block, Expr, ItemKind, Node}; use rustc_lint::LateContext; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, loop_block: &Block<'_>) { + let parent_hir_id = cx.tcx.parent_hir_id(expr.hir_id); + if let Node::Item(parent_node) = cx.tcx.hir_node(parent_hir_id) + && matches!(parent_node.kind, ItemKind::Fn { .. }) + && let attrs = cx.tcx.hir_attrs(parent_hir_id) + && attrs.iter().any(|attr| attr.has_name(sym::rustc_intrinsic)) + { + // Intrinsic functions are expanded into an empty loop when lowering the AST + // to simplify the job of later passes which might expect any function to have a body. + return; + } + if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) { let msg = "empty `loop {}` wastes CPU cycles"; let help = if is_no_std_crate(cx) { diff --git a/src/tools/clippy/clippy_lints/src/manual_let_else.rs b/src/tools/clippy/clippy_lints/src/manual_let_else.rs index 9ff82cdcb66..1f9a943f13d 100644 --- a/src/tools/clippy/clippy_lints/src/manual_let_else.rs +++ b/src/tools/clippy/clippy_lints/src/manual_let_else.rs @@ -245,17 +245,36 @@ fn replace_in_pattern( } match pat.kind { - PatKind::Binding(_ann, _id, binding_name, opt_subpt) => { - let Some((pat_to_put, binding_mode)) = ident_map.get(&binding_name.name) else { - break 'a; - }; - let sn_pfx = binding_mode.prefix_str(); - let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app); - if let Some(subpt) = opt_subpt { - let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false); - return format!("{sn_pfx}{sn_ptp} @ {subpt}"); + PatKind::Binding(ann, _id, binding_name, opt_subpt) => { + match (ident_map.get(&binding_name.name), opt_subpt) { + (Some((pat_to_put, binding_mode)), opt_subpt) => { + let sn_pfx = binding_mode.prefix_str(); + let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app); + if let Some(subpt) = opt_subpt { + let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false); + return format!("{sn_pfx}{sn_ptp} @ {subpt}"); + } + return format!("{sn_pfx}{sn_ptp}"); + }, + (None, Some(subpt)) => { + let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false); + // scanning for a value that matches is not sensitive to order + #[expect(rustc::potential_query_instability)] + if ident_map.values().any(|(other_pat, _)| { + if let PatKind::Binding(_, _, other_name, _) = other_pat.kind { + other_name == binding_name + } else { + false + } + }) { + // this name is shadowed, and, therefore, not usable + return subpt; + } + let binding_pfx = ann.prefix_str(); + return format!("{binding_pfx}{binding_name} @ {subpt}"); + }, + (None, None) => break 'a, } - return format!("{sn_pfx}{sn_ptp}"); }, PatKind::Or(pats) => { let patterns = pats diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs index 51696b38880..2d52a93f34e 100644 --- a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs +++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs @@ -4,16 +4,15 @@ use clippy_utils::is_doc_hidden; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_indent; use itertools::Itertools; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::def_id::LocalDefId; use rustc_span::Span; -use rustc_attr_data_structures::find_attr; -use rustc_attr_data_structures::AttributeKind; +use rustc_span::def_id::LocalDefId; declare_clippy_lint! { /// ### What it does diff --git a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs index ede68f30941..ae277da089f 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs @@ -332,9 +332,7 @@ impl<'a> NormalizedPat<'a> { // TODO: Handle negative integers. They're currently treated as a wild match. PatExprKind::Lit { lit, negated: false } => match lit.node { LitKind::Str(sym, _) => Self::LitStr(sym), - LitKind::ByteStr(byte_sym, _) | LitKind::CStr(byte_sym, _) => { - Self::LitBytes(byte_sym) - } + LitKind::ByteStr(byte_sym, _) | LitKind::CStr(byte_sym, _) => Self::LitBytes(byte_sym), LitKind::Byte(val) => Self::LitInt(val.into()), LitKind::Char(val) => Self::LitInt(val.into()), LitKind::Int(val, _) => Self::LitInt(val.get()), diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs b/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs index 4a61c223d2c..93325ca488e 100644 --- a/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs +++ b/src/tools/clippy/clippy_lints/src/methods/manual_is_variant_and.rs @@ -1,14 +1,16 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::get_parent_expr; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::{snippet, snippet_opt}; +use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{get_parent_expr, sym}; +use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::{BytePos, Span, sym}; +use rustc_span::{Span, Symbol}; use super::MANUAL_IS_VARIANT_AND; @@ -62,54 +64,154 @@ pub(super) fn check( ); } -fn emit_lint(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, method_span: Span, is_option: bool) { - if let Some(before_map_snippet) = snippet_opt(cx, parent.span.with_hi(method_span.lo())) - && let Some(after_map_snippet) = snippet_opt(cx, method_span.with_lo(method_span.lo() + BytePos(3))) - { - span_lint_and_sugg( - cx, - MANUAL_IS_VARIANT_AND, - parent.span, - format!( - "called `.map() {}= {}()`", - if op == BinOpKind::Eq { '=' } else { '!' }, - if is_option { "Some" } else { "Ok" }, - ), - "use", - if is_option && op == BinOpKind::Ne { - format!("{before_map_snippet}is_none_or{after_map_snippet}",) - } else { +#[derive(Clone, Copy, PartialEq)] +enum Flavor { + Option, + Result, +} + +impl Flavor { + const fn symbol(self) -> Symbol { + match self { + Self::Option => sym::Option, + Self::Result => sym::Result, + } + } + + const fn positive(self) -> Symbol { + match self { + Self::Option => sym::Some, + Self::Result => sym::Ok, + } + } +} + +#[derive(Clone, Copy, PartialEq)] +enum Op { + Eq, + Ne, +} + +impl std::fmt::Display for Op { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Eq => write!(f, "=="), + Self::Ne => write!(f, "!="), + } + } +} + +impl TryFrom<BinOpKind> for Op { + type Error = (); + fn try_from(op: BinOpKind) -> Result<Self, Self::Error> { + match op { + BinOpKind::Eq => Ok(Self::Eq), + BinOpKind::Ne => Ok(Self::Ne), + _ => Err(()), + } + } +} + +/// Represents the argument of the `.map()` function, as a closure or as a path +/// in case η-reduction is used. +enum MapFunc<'hir> { + Closure(&'hir Closure<'hir>), + Path(&'hir Expr<'hir>), +} + +impl<'hir> TryFrom<&'hir Expr<'hir>> for MapFunc<'hir> { + type Error = (); + + fn try_from(expr: &'hir Expr<'hir>) -> Result<Self, Self::Error> { + match expr.kind { + ExprKind::Closure(closure) => Ok(Self::Closure(closure)), + ExprKind::Path(_) => Ok(Self::Path(expr)), + _ => Err(()), + } + } +} + +impl<'hir> MapFunc<'hir> { + /// Build a suggestion suitable for use in a `.map()`-like function. η-expansion will be applied + /// as needed. + fn sugg(self, cx: &LateContext<'hir>, invert: bool, app: &mut Applicability) -> String { + match self { + Self::Closure(closure) => { + let body = Sugg::hir_with_applicability(cx, cx.tcx.hir_body(closure.body).value, "..", app); format!( - "{}{before_map_snippet}{}{after_map_snippet}", - if op == BinOpKind::Eq { "" } else { "!" }, - if is_option { "is_some_and" } else { "is_ok_and" }, + "{} {}", + snippet_with_applicability(cx, closure.fn_decl_span, "|..|", app), + if invert { !body } else { body } ) }, - Applicability::MachineApplicable, - ); + Self::Path(expr) => { + let path = snippet_with_applicability(cx, expr.span, "_", app); + if invert { + format!("|x| !{path}(x)") + } else { + path.to_string() + } + }, + } } } +fn emit_lint<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + op: Op, + flavor: Flavor, + in_some_or_ok: bool, + map_func: MapFunc<'tcx>, + recv: &Expr<'_>, +) { + let mut app = Applicability::MachineApplicable; + let recv = snippet_with_applicability(cx, recv.span, "_", &mut app); + + let (invert_expr, method, invert_body) = match (flavor, op) { + (Flavor::Option, Op::Eq) => (false, "is_some_and", !in_some_or_ok), + (Flavor::Option, Op::Ne) => (false, "is_none_or", in_some_or_ok), + (Flavor::Result, Op::Eq) => (false, "is_ok_and", !in_some_or_ok), + (Flavor::Result, Op::Ne) => (true, "is_ok_and", !in_some_or_ok), + }; + span_lint_and_sugg( + cx, + MANUAL_IS_VARIANT_AND, + span, + format!("called `.map() {op} {pos}()`", pos = flavor.positive(),), + "use", + format!( + "{inversion}{recv}.{method}({body})", + inversion = if invert_expr { "!" } else { "" }, + body = map_func.sugg(cx, invert_body, &mut app), + ), + app, + ); +} + pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) { if let Some(parent_expr) = get_parent_expr(cx, expr) && let ExprKind::Binary(op, left, right) = parent_expr.kind - && matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) && op.span.eq_ctxt(expr.span) + && let Ok(op) = Op::try_from(op.node) { // Check `left` and `right` expression in any order, and for `Option` and `Result` for (expr1, expr2) in [(left, right), (right, left)] { - for item in [sym::Option, sym::Result] { - if let ExprKind::Call(call, ..) = expr1.kind + for flavor in [Flavor::Option, Flavor::Result] { + if let ExprKind::Call(call, [arg]) = expr1.kind + && let ExprKind::Lit(lit) = arg.kind + && let LitKind::Bool(bool_cst) = lit.node && let ExprKind::Path(QPath::Resolved(_, path)) = call.kind && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res && let ty = cx.typeck_results().expr_ty(expr1) && let ty::Adt(adt, args) = ty.kind() - && cx.tcx.is_diagnostic_item(item, adt.did()) + && cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did()) && args.type_at(0).is_bool() - && let ExprKind::MethodCall(_, recv, _, span) = expr2.kind - && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), item) + && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), flavor.symbol()) + && let Ok(map_func) = MapFunc::try_from(map_expr) { - return emit_lint(cx, op.node, parent_expr, span, item == sym::Option); + return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); } } } diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs index 2139466ce74..6ce7dd3d4d0 100644 --- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs @@ -136,7 +136,7 @@ pub(super) fn check<'tcx>( fun_span: Option<Span>, ) -> bool { // (path, fn_has_argument, methods, suffix) - const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 5] = [ + const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [ (sym::BTreeEntry, false, &[sym::or_insert], "with"), (sym::HashMapEntry, false, &[sym::or_insert], "with"), ( @@ -146,7 +146,9 @@ pub(super) fn check<'tcx>( "else", ), (sym::Option, false, &[sym::get_or_insert], "with"), + (sym::Option, true, &[sym::and], "then"), (sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"), + (sym::Result, true, &[sym::and], "then"), ]; if KNOW_TYPES.iter().any(|k| k.2.contains(&name)) diff --git a/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs b/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs index df8544f9220..54f38a322b8 100644 --- a/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs +++ b/src/tools/clippy/clippy_lints/src/methods/return_and_then.rs @@ -1,5 +1,5 @@ use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::{self as hir, Node}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArg, Ty}; use rustc_span::sym; @@ -9,7 +9,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability}; use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::visitors::for_each_unconsumed_temporary; -use clippy_utils::{get_parent_expr, peel_blocks}; +use clippy_utils::{peel_blocks, potential_return_of_enclosing_body}; use super::RETURN_AND_THEN; @@ -21,7 +21,7 @@ pub(super) fn check<'tcx>( recv: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'_>, ) { - if cx.tcx.hir_get_fn_id_for_return_block(expr.hir_id).is_none() { + if !potential_return_of_enclosing_body(cx, expr) { return; } @@ -55,15 +55,28 @@ pub(super) fn check<'tcx>( None => &body_snip, }; - // If suggestion is going to get inserted as part of a `return` expression, it must be blockified. - let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) { - let base_indent = indent_of(cx, parent_expr.span); + // If suggestion is going to get inserted as part of a `return` expression or as a match expression + // arm, it must be blockified. + let (parent_span_for_indent, opening_paren, closing_paren) = match cx.tcx.parent_hir_node(expr.hir_id) { + Node::Expr(parent_expr) if matches!(parent_expr.kind, hir::ExprKind::Break(..)) => { + (Some(parent_expr.span), "(", ")") + }, + Node::Expr(parent_expr) => (Some(parent_expr.span), "", ""), + Node::Arm(match_arm) => (Some(match_arm.span), "", ""), + _ => (None, "", ""), + }; + let sugg = if let Some(span) = parent_span_for_indent { + let base_indent = indent_of(cx, span); let inner_indent = base_indent.map(|i| i + 4); format!( "{}\n{}\n{}", - reindent_multiline(&format!("{{\nlet {arg_snip} = {recv_snip}?;"), true, inner_indent), + reindent_multiline( + &format!("{opening_paren}{{\nlet {arg_snip} = {recv_snip}?;"), + true, + inner_indent + ), reindent_multiline(inner, false, inner_indent), - reindent_multiline("}", false, base_indent), + reindent_multiline(&format!("}}{closing_paren}"), false, base_indent), ) } else { format!( diff --git a/src/tools/clippy/clippy_lints/src/methods/swap_with_temporary.rs b/src/tools/clippy/clippy_lints/src/methods/swap_with_temporary.rs index de729fb343a..e378cbd6ae0 100644 --- a/src/tools/clippy/clippy_lints/src/methods/swap_with_temporary.rs +++ b/src/tools/clippy/clippy_lints/src/methods/swap_with_temporary.rs @@ -4,6 +4,7 @@ use rustc_ast::BorrowKind; use rustc_errors::{Applicability, Diag}; use rustc_hir::{Expr, ExprKind, Node, QPath}; use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::Adjust; use rustc_span::sym; use super::SWAP_WITH_TEMPORARY; @@ -11,12 +12,12 @@ use super::SWAP_WITH_TEMPORARY; const MSG_TEMPORARY: &str = "this expression returns a temporary value"; const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value"; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'_>, args: &'tcx [Expr<'_>]) { if let ExprKind::Path(QPath::Resolved(_, func_path)) = func.kind && let Some(func_def_id) = func_path.res.opt_def_id() && cx.tcx.is_diagnostic_item(sym::mem_swap, func_def_id) { - match (ArgKind::new(&args[0]), ArgKind::new(&args[1])) { + match (ArgKind::new(cx, &args[0]), ArgKind::new(cx, &args[1])) { (ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => { emit_lint_useless(cx, expr, &args[0], &args[1], left_temp, right_temp); }, @@ -28,10 +29,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args } enum ArgKind<'tcx> { - // Mutable reference to a place, coming from a macro - RefMutToPlaceAsMacro(&'tcx Expr<'tcx>), - // Place behind a mutable reference - RefMutToPlace(&'tcx Expr<'tcx>), + // Mutable reference to a place, coming from a macro, and number of dereferences to use + RefMutToPlaceAsMacro(&'tcx Expr<'tcx>, usize), + // Place behind a mutable reference, and number of dereferences to use + RefMutToPlace(&'tcx Expr<'tcx>, usize), // Temporary value behind a mutable reference RefMutToTemp(&'tcx Expr<'tcx>), // Any other case @@ -39,13 +40,29 @@ enum ArgKind<'tcx> { } impl<'tcx> ArgKind<'tcx> { - fn new(arg: &'tcx Expr<'tcx>) -> Self { - if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind { - if target.is_syntactic_place_expr() { + /// Build a new `ArgKind` from `arg`. There must be no false positive when returning a + /// `ArgKind::RefMutToTemp` variant, as this may cause a spurious lint to be emitted. + fn new(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Self { + if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind + && let adjustments = cx.typeck_results().expr_adjustments(arg) + && adjustments + .first() + .is_some_and(|adj| matches!(adj.kind, Adjust::Deref(None))) + && adjustments + .last() + .is_some_and(|adj| matches!(adj.kind, Adjust::Borrow(_))) + { + let extra_derefs = adjustments[1..adjustments.len() - 1] + .iter() + .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) + .count(); + // If a deref is used, `arg` might be a place expression. For example, a mutex guard + // would dereference into the mutex content which is probably not temporary. + if target.is_syntactic_place_expr() || extra_derefs > 0 { if arg.span.from_expansion() { - ArgKind::RefMutToPlaceAsMacro(arg) + ArgKind::RefMutToPlaceAsMacro(arg, extra_derefs) } else { - ArgKind::RefMutToPlace(target) + ArgKind::RefMutToPlace(target, extra_derefs) } } else { ArgKind::RefMutToTemp(target) @@ -106,10 +123,15 @@ fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>, let mut applicability = Applicability::MachineApplicable; let ctxt = expr.span.ctxt(); let assign_target = match target { - ArgKind::Expr(target) | ArgKind::RefMutToPlaceAsMacro(target) => { - Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref() - }, - ArgKind::RefMutToPlace(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability), + ArgKind::Expr(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref(), + ArgKind::RefMutToPlaceAsMacro(arg, derefs) => (0..*derefs).fold( + Sugg::hir_with_context(cx, arg, ctxt, "_", &mut applicability).deref(), + |sugg, _| sugg.deref(), + ), + ArgKind::RefMutToPlace(target, derefs) => (0..*derefs).fold( + Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability), + |sugg, _| sugg.deref(), + ), ArgKind::RefMutToTemp(_) => unreachable!(), }; let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability); diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_map_or.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_map_or.rs index b90748dd158..4a9007c607c 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_map_or.rs @@ -62,7 +62,8 @@ pub(super) fn check<'a>( let ext_def_span = def.span.until(map.span); - let (sugg, method, applicability) = if let ExprKind::Closure(map_closure) = map.kind + let (sugg, method, applicability) = if cx.typeck_results().expr_adjustments(recv).is_empty() + && let ExprKind::Closure(map_closure) = map.kind && let closure_body = cx.tcx.hir_body(map_closure.body) && let closure_body_value = closure_body.value.peel_blocks() && let ExprKind::Binary(op, l, r) = closure_body_value.kind diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs index 3ed4b1c2ea9..b3aa1a7286a 100644 --- a/src/tools/clippy/clippy_lints/src/needless_bool.rs +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -199,11 +199,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { let mut applicability = Applicability::MachineApplicable; let cond = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability); let lhs = snippet_with_applicability(cx, lhs_a.span, "..", &mut applicability); - let sugg = if a == b { + let mut sugg = if a == b { format!("{cond}; {lhs} = {a:?};") } else { format!("{lhs} = {};", if a { cond } else { !cond }) }; + + if is_else_clause(cx.tcx, e) { + sugg = format!("{{ {sugg} }}"); + } + span_lint_and_sugg( cx, NEEDLESS_BOOL_ASSIGN, diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs index 442280f9998..946114e1041 100644 --- a/src/tools/clippy/clippy_lints/src/neg_multiply.rs +++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs @@ -1,13 +1,13 @@ use clippy_utils::consts::{self, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_context; +use clippy_utils::get_parent_expr; +use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -33,6 +33,19 @@ declare_clippy_lint! { declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]); +fn is_in_parens_with_postfix(cx: &LateContext<'_>, mul_expr: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, mul_expr) { + let mult_snippet = snippet(cx, mul_expr.span, ""); + if has_enclosing_paren(&mult_snippet) + && let ExprKind::MethodCall(_, _, _, _) = parent.kind + { + return true; + } + } + + false +} + impl<'tcx> LateLintPass<'tcx> for NegMultiply { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Binary(ref op, left, right) = e.kind @@ -40,15 +53,15 @@ impl<'tcx> LateLintPass<'tcx> for NegMultiply { { match (&left.kind, &right.kind) { (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {}, - (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right), - (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left), + (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e, lit, right), + (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e, lit, left), _ => {}, } } } } -fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { +fn check_mul(cx: &LateContext<'_>, mul_expr: &Expr<'_>, lit: &Expr<'_>, exp: &Expr<'_>) { const F16_ONE: u16 = 1.0_f16.to_bits(); const F128_ONE: u128 = 1.0_f128.to_bits(); if let ExprKind::Lit(l) = lit.kind @@ -63,8 +76,19 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { && cx.typeck_results().expr_ty(exp).is_numeric() { let mut applicability = Applicability::MachineApplicable; - let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability); - let suggestion = if !from_macro && cx.precedence(exp) < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) { + let (snip, from_macro) = snippet_with_context(cx, exp.span, mul_expr.span.ctxt(), "..", &mut applicability); + + let needs_parens_for_postfix = is_in_parens_with_postfix(cx, mul_expr); + + let suggestion = if needs_parens_for_postfix { + // Special case: when the multiplication is in parentheses followed by a method call + // we need to preserve the grouping but negate the inner expression. + // Consider this expression: `((a.delta - 0.5).abs() * -1.0).total_cmp(&1.0)` + // We need to end up with: `(-(a.delta - 0.5).abs()).total_cmp(&1.0)` + // Otherwise, without the parentheses we would try to negate an Ordering: + // `-(a.delta - 0.5).abs().total_cmp(&1.0)` + format!("(-{snip})") + } else if !from_macro && cx.precedence(exp) < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) { format!("-({snip})") } else { format!("-{snip}") @@ -72,7 +96,7 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { span_lint_and_sugg( cx, NEG_MULTIPLY, - span, + mul_expr.span, "this multiplication by -1 can be written more succinctly", "consider using", suggestion, diff --git a/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs index 02c48166131..72e6503e7e4 100644 --- a/src/tools/clippy/clippy_lints/src/no_effect.rs +++ b/src/tools/clippy/clippy_lints/src/no_effect.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::has_drop; +use clippy_utils::ty::{expr_type_is_certain, has_drop}; use clippy_utils::{ in_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks, }; @@ -340,11 +340,13 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec }, ExprKind::Array(v) | ExprKind::Tup(v) => Some(v.iter().collect()), ExprKind::Repeat(inner, _) - | ExprKind::Cast(inner, _) | ExprKind::Type(inner, _) | ExprKind::Unary(_, inner) | ExprKind::Field(inner, _) | ExprKind::AddrOf(_, _, inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])), + ExprKind::Cast(inner, _) if expr_type_is_certain(cx, inner) => { + reduce_expression(cx, inner).or_else(|| Some(vec![inner])) + }, ExprKind::Struct(_, fields, ref base) => { if has_drop(cx, cx.typeck_results().expr_ty(expr)) { None diff --git a/src/tools/clippy/clippy_lints/src/operators/op_ref.rs b/src/tools/clippy/clippy_lints/src/operators/op_ref.rs index 0faa7b9e646..21e1ab0f4f2 100644 --- a/src/tools/clippy/clippy_lints/src/operators/op_ref.rs +++ b/src/tools/clippy/clippy_lints/src/operators/op_ref.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::get_enclosing_block; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -61,12 +61,13 @@ pub(crate) fn check<'tcx>( e.span, "needlessly taken reference of both operands", |diag| { - let lsnip = snippet(cx, l.span, "...").to_string(); - let rsnip = snippet(cx, r.span, "...").to_string(); + let mut applicability = Applicability::MachineApplicable; + let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability); + let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability); diag.multipart_suggestion( "use the values directly", - vec![(left.span, lsnip), (right.span, rsnip)], - Applicability::MachineApplicable, + vec![(left.span, lsnip.to_string()), (right.span, rsnip.to_string())], + applicability, ); }, ); @@ -80,13 +81,9 @@ pub(crate) fn check<'tcx>( e.span, "needlessly taken reference of left operand", |diag| { - let lsnip = snippet(cx, l.span, "...").to_string(); - diag.span_suggestion( - left.span, - "use the left value directly", - lsnip, - Applicability::MachineApplicable, - ); + let mut applicability = Applicability::MachineApplicable; + let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability); + diag.span_suggestion(left.span, "use the left value directly", lsnip, applicability); }, ); } else if !lcpy @@ -99,7 +96,8 @@ pub(crate) fn check<'tcx>( e.span, "needlessly taken reference of right operand", |diag| { - let rsnip = snippet(cx, r.span, "...").to_string(); + let mut applicability = Applicability::MachineApplicable; + let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability); diag.span_suggestion( right.span, "use the right value directly", @@ -131,7 +129,8 @@ pub(crate) fn check<'tcx>( e.span, "needlessly taken reference of left operand", |diag| { - let lsnip = snippet(cx, l.span, "...").to_string(); + let mut applicability = Applicability::MachineApplicable; + let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability); diag.span_suggestion( left.span, "use the left value directly", @@ -158,7 +157,8 @@ pub(crate) fn check<'tcx>( && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) { span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| { - let rsnip = snippet(cx, r.span, "...").to_string(); + let mut applicability = Applicability::MachineApplicable; + let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability); diag.span_suggestion( right.span, "use the right value directly", diff --git a/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs b/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs index 84597269a58..1c23fe998ad 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs @@ -5,9 +5,7 @@ use hir::Param; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor}; -use rustc_hir::{ - ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, Node, intravisit as hir_visit, -}; +use rustc_hir::{ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, intravisit as hir_visit}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; use rustc_middle::ty; @@ -198,15 +196,15 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall { hint = hint.asyncify(); } - let is_in_fn_call_arg = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) { - matches!(expr.kind, ExprKind::Call(_, _)) - } else { - false - }; - - // avoid clippy::double_parens - if !is_in_fn_call_arg { - hint = hint.maybe_paren(); + // If the closure body is a block with a single expression, suggest just the inner expression, + // not the block. Example: `(|| { Some(true) })()` should suggest + // `Some(true)` + if let ExprKind::Block(block, _) = body.kind + && block.stmts.is_empty() + && let Some(expr) = block.expr + { + hint = Sugg::hir_with_context(cx, expr, full_expr.span.ctxt(), "..", &mut applicability) + .maybe_paren(); } diag.span_suggestion(full_expr.span, "try doing something like", hint, applicability); 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 442b3250d86..cf70e883bd0 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 @@ -1,10 +1,10 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::Msrv; use rustc_attr_data_structures::{StabilityLevel, StableSince}; use rustc_errors::Applicability; -use rustc_hir::def::Res; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::{Block, Body, HirId, Path, PathSegment}; use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; @@ -88,49 +88,52 @@ declare_clippy_lint! { } pub struct StdReexports { - lint_point: (Span, Option<LintPoint>), + lint_points: Option<(Span, Vec<LintPoint>)>, msrv: Msrv, } impl StdReexports { pub fn new(conf: &'static Conf) -> Self { Self { - lint_point: Default::default(), + lint_points: Option::default(), msrv: conf.msrv, } } - fn lint_if_finish(&mut self, cx: &LateContext<'_>, (span, item): (Span, Option<LintPoint>)) { - if span.source_equal(self.lint_point.0) { - return; + fn lint_if_finish(&mut self, cx: &LateContext<'_>, krate: Span, lint_point: LintPoint) { + match &mut self.lint_points { + Some((prev_krate, prev_lints)) if prev_krate.overlaps(krate) => { + prev_lints.push(lint_point); + }, + _ => emit_lints(cx, self.lint_points.replace((krate, vec![lint_point]))), } - - if !self.lint_point.0.is_dummy() { - emit_lints(cx, &self.lint_point); - } - - self.lint_point = (span, item); } } impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]); -type LintPoint = (&'static Lint, &'static str, &'static str); +#[derive(Debug)] +enum LintPoint { + Available(Span, &'static Lint, &'static str, &'static str), + Conflict, +} impl<'tcx> LateLintPass<'tcx> for StdReexports { fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) { - if let Res::Def(_, def_id) = path.res + if let Res::Def(def_kind, def_id) = path.res && let Some(first_segment) = get_first_segment(path) && is_stable(cx, def_id, self.msrv) && !path.span.in_external_macro(cx.sess().source_map()) && !is_from_proc_macro(cx, &first_segment.ident) + && !matches!(def_kind, DefKind::Macro(_)) + && let Some(last_segment) = path.segments.last() { let (lint, used_mod, replace_with) = match first_segment.ident.name { sym::std => match cx.tcx.crate_name(def_id.krate) { sym::core => (STD_INSTEAD_OF_CORE, "std", "core"), sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"), _ => { - self.lint_if_finish(cx, (first_segment.ident.span, None)); + self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); return; }, }, @@ -138,44 +141,84 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { if cx.tcx.crate_name(def_id.krate) == sym::core { (ALLOC_INSTEAD_OF_CORE, "alloc", "core") } else { - self.lint_if_finish(cx, (first_segment.ident.span, None)); + self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); return; } }, - _ => return, + _ => { + self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict); + return; + }, }; - self.lint_if_finish(cx, (first_segment.ident.span, Some((lint, used_mod, replace_with)))); + self.lint_if_finish( + cx, + first_segment.ident.span, + LintPoint::Available(last_segment.ident.span, lint, used_mod, replace_with), + ); } } fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) { - self.lint_if_finish(cx, Default::default()); + emit_lints(cx, self.lint_points.take()); } fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) { - self.lint_if_finish(cx, Default::default()); + emit_lints(cx, self.lint_points.take()); } fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - self.lint_if_finish(cx, Default::default()); + emit_lints(cx, self.lint_points.take()); } } -fn emit_lints(cx: &LateContext<'_>, (span, item): &(Span, Option<LintPoint>)) { - let Some((lint, used_mod, replace_with)) = item else { +fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>) { + let Some((krate_span, lint_points)) = lint_points else { return; }; - span_lint_and_sugg( - cx, - lint, - *span, - format!("used import from `{used_mod}` instead of `{replace_with}`"), - format!("consider importing the item from `{replace_with}`"), - (*replace_with).to_string(), - Applicability::MachineApplicable, - ); + let mut lint: Option<(&'static Lint, &'static str, &'static str)> = None; + let mut has_conflict = false; + for lint_point in &lint_points { + match lint_point { + LintPoint::Available(_, l, used_mod, replace_with) + if lint.is_none_or(|(prev_l, ..)| l.name == prev_l.name) => + { + lint = Some((l, used_mod, replace_with)); + }, + _ => { + has_conflict = true; + break; + }, + } + } + + if !has_conflict && let Some((lint, used_mod, replace_with)) = lint { + span_lint_and_sugg( + cx, + lint, + krate_span, + format!("used import from `{used_mod}` instead of `{replace_with}`"), + format!("consider importing the item from `{replace_with}`"), + (*replace_with).to_string(), + Applicability::MachineApplicable, + ); + return; + } + + for lint_point in lint_points { + let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else { + continue; + }; + span_lint_and_help( + cx, + lint, + span, + format!("used import from `{used_mod}` instead of `{replace_with}`"), + None, + format!("consider importing the item from `{replace_with}`"), + ); + } } /// Returns the first named segment of a [`Path`]. diff --git a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs index 6cc4b589a72..45c0d459d90 100644 --- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -143,7 +143,8 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { if let Some(tail) = block.expr && !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id) && !tail.span.in_external_macro(cx.tcx.sess.source_map()) - && let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, tail.span, tail.hir_id) + && let HasSafetyComment::Yes(pos) = + stmt_has_safety_comment(cx, tail.span, tail.hir_id, self.accept_comment_above_attributes) && let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos) { span_lint_and_then( @@ -167,7 +168,8 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { }; if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id) && !stmt.span.in_external_macro(cx.tcx.sess.source_map()) - && let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id) + && let HasSafetyComment::Yes(pos) = + stmt_has_safety_comment(cx, stmt.span, stmt.hir_id, self.accept_comment_above_attributes) && let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos) { span_lint_and_then( @@ -540,7 +542,12 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf /// Checks if the lines immediately preceding the item contain a safety comment. #[allow(clippy::collapsible_match)] -fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment { +fn stmt_has_safety_comment( + cx: &LateContext<'_>, + span: Span, + hir_id: HirId, + accept_comment_above_attributes: bool, +) -> HasSafetyComment { match span_from_macro_expansion_has_safety_comment(cx, span) { HasSafetyComment::Maybe => (), has_safety_comment => return has_safety_comment, @@ -555,6 +562,13 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H _ => return HasSafetyComment::Maybe, }; + // if span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attrib + // } + let mut span = span; + if accept_comment_above_attributes { + span = include_attrs_in_span(cx, hir_id, span); + } + let source_map = cx.sess().source_map(); if let Some(comment_start) = comment_start && let Ok(unsafe_line) = source_map.lookup_line(span.lo()) diff --git a/src/tools/clippy/clippy_lints/src/unused_async.rs b/src/tools/clippy/clippy_lints/src/unused_async.rs index 8ceaa3dc58e..e67afc7f5a8 100644 --- a/src/tools/clippy/clippy_lints/src/unused_async.rs +++ b/src/tools/clippy/clippy_lints/src/unused_async.rs @@ -169,7 +169,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { let iter = self .unused_async_fns .iter() - .filter(|UnusedAsyncFn { def_id, .. }| (!self.async_fns_as_value.contains(def_id))); + .filter(|UnusedAsyncFn { def_id, .. }| !self.async_fns_as_value.contains(def_id)); for fun in iter { span_lint_hir_and_then( diff --git a/src/tools/clippy/clippy_lints_internal/src/symbols.rs b/src/tools/clippy/clippy_lints_internal/src/symbols.rs index 7b5d58824c3..74712097e71 100644 --- a/src/tools/clippy/clippy_lints_internal/src/symbols.rs +++ b/src/tools/clippy/clippy_lints_internal/src/symbols.rs @@ -65,7 +65,7 @@ pub struct Symbols { impl_lint_pass!(Symbols => [INTERNING_LITERALS, SYMBOL_AS_STR]); impl Symbols { - fn lit_suggestion(&self, lit: &Lit) -> Option<(Span, String)> { + fn lit_suggestion(&self, lit: Lit) -> Option<(Span, String)> { if let LitKind::Str(name, _) = lit.node { let sugg = if let Some((prefix, name)) = self.symbol_map.get(&name.as_u32()) { format!("{prefix}::{name}") diff --git a/src/tools/clippy/clippy_test_deps/Cargo.toml b/src/tools/clippy/clippy_test_deps/Cargo.toml new file mode 100644 index 00000000000..f41334f0ade --- /dev/null +++ b/src/tools/clippy/clippy_test_deps/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "clippy_test_deps" +version = "0.1.0" +edition = "2021" + +# Add dependencies here to make them available in ui tests. + +[dependencies] +regex = "1.5.5" +serde = { version = "1.0.145", features = ["derive"] } +if_chain = "1.0" +quote = "1.0.25" +syn = { version = "2.0", features = ["full"] } +futures = "0.3" +parking_lot = "0.12" +tokio = { version = "1", features = ["io-util"] } +itertools = "0.12" diff --git a/src/tools/clippy/clippy_test_deps/src/main.rs b/src/tools/clippy/clippy_test_deps/src/main.rs new file mode 100644 index 00000000000..f328e4d9d04 --- /dev/null +++ b/src/tools/clippy/clippy_test_deps/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/clippy/clippy_utils/README.md b/src/tools/clippy/clippy_utils/README.md index 649748d1534..645b644d9f4 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-06-26 +nightly-2025-07-10 ``` <!-- end autogenerated nightly --> diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs index 4c7a589e185..34472eaab93 100644 --- a/src/tools/clippy/clippy_utils/src/attrs.rs +++ b/src/tools/clippy/clippy_utils/src/attrs.rs @@ -1,5 +1,8 @@ +use crate::source::SpanRangeExt; +use crate::{sym, tokenize_with_text}; use rustc_ast::attr; use rustc_ast::attr::AttributeExt; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_errors::Applicability; use rustc_lexer::TokenKind; use rustc_lint::LateContext; @@ -7,10 +10,6 @@ use rustc_middle::ty::{AdtDef, TyCtxt}; use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; -use rustc_attr_data_structures::find_attr; -use crate::source::SpanRangeExt; -use crate::{sym, tokenize_with_text}; -use rustc_attr_data_structures::AttributeKind; /// Deprecation status of attributes known by Clippy. pub enum DeprecationStatus { @@ -168,7 +167,8 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { adt.is_variant_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..)) || adt.variants().iter().any(|variant_def| { - variant_def.is_field_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..)) + variant_def.is_field_list_non_exhaustive() + || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..)) }) || adt .all_fields() diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index ba0376e4d40..94b7055ad20 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -304,9 +304,7 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option<Ty<'tcx>>) -> Constan match *lit { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), - LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => { - Constant::Binary(s.as_byte_str().to_vec()) - } + LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => Constant::Binary(s.as_byte_str().to_vec()), LitKind::Char(c) => Constant::Char(c), LitKind::Int(n, _) => Constant::Int(n.get()), LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { @@ -568,9 +566,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } else { match &lit.node { LitKind::Str(is, _) => Some(is.is_empty()), - LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => { - Some(s.as_byte_str().is_empty()) - } + LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => Some(s.as_byte_str().is_empty()), _ => None, } } diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 0ca494f16e3..6f19ce80cf6 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -2,6 +2,7 @@ use crate::consts::ConstEvalCtxt; use crate::macros::macro_backtrace; use crate::source::{SpanRange, SpanRangeExt, walk_span_to_context}; use crate::tokenize_with_text; +use rustc_ast::ast; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHasher; use rustc_hir::MatchSource::TryDesugar; @@ -9,8 +10,8 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeKind, - Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, - TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind, + Node, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, + StructTailExpr, TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind, }; use rustc_lexer::{TokenKind, tokenize}; use rustc_lint::LateContext; @@ -1004,8 +1005,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(e); } }, - ExprKind::Match(e, arms, s) => { - self.hash_expr(e); + ExprKind::Match(scrutinee, arms, _) => { + self.hash_expr(scrutinee); for arm in *arms { self.hash_pat(arm.pat); @@ -1014,8 +1015,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } self.hash_expr(arm.body); } - - s.hash(&mut self.s); }, ExprKind::MethodCall(path, receiver, args, _fn_span) => { self.hash_name(path.ident.name); @@ -1058,8 +1057,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { ExprKind::Use(expr, _) => { self.hash_expr(expr); }, - ExprKind::Unary(lop, le) => { - std::mem::discriminant(lop).hash(&mut self.s); + ExprKind::Unary(l_op, le) => { + std::mem::discriminant(l_op).hash(&mut self.s); self.hash_expr(le); }, ExprKind::UnsafeBinderCast(kind, expr, ty) => { @@ -1394,3 +1393,70 @@ fn eq_span_tokens( } f(cx, left.into_range(), right.into_range(), pred) } + +/// Returns true if the expression contains ambiguous literals (unsuffixed float or int literals) +/// that could be interpreted as either f32/f64 or i32/i64 depending on context. +pub fn has_ambiguous_literal_in_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Path(ref qpath) => { + if let Res::Local(hir_id) = cx.qpath_res(qpath, expr.hir_id) + && let Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id) + && local.ty.is_none() + && let Some(init) = local.init + { + return has_ambiguous_literal_in_expr(cx, init); + } + false + }, + ExprKind::Lit(lit) => matches!( + lit.node, + ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) | ast::LitKind::Int(_, ast::LitIntType::Unsuffixed) + ), + + ExprKind::Array(exprs) | ExprKind::Tup(exprs) => exprs.iter().any(|e| has_ambiguous_literal_in_expr(cx, e)), + + ExprKind::Assign(lhs, rhs, _) | ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Binary(_, lhs, rhs) => { + has_ambiguous_literal_in_expr(cx, lhs) || has_ambiguous_literal_in_expr(cx, rhs) + }, + + ExprKind::Unary(_, e) + | ExprKind::Cast(e, _) + | ExprKind::Type(e, _) + | ExprKind::DropTemps(e) + | ExprKind::AddrOf(_, _, e) + | ExprKind::Field(e, _) + | ExprKind::Index(e, _, _) + | ExprKind::Yield(e, _) => has_ambiguous_literal_in_expr(cx, e), + + ExprKind::MethodCall(_, receiver, args, _) | ExprKind::Call(receiver, args) => { + has_ambiguous_literal_in_expr(cx, receiver) || args.iter().any(|e| has_ambiguous_literal_in_expr(cx, e)) + }, + + ExprKind::Closure(Closure { body, .. }) => { + let body = cx.tcx.hir_body(*body); + let closure_expr = crate::peel_blocks(body.value); + has_ambiguous_literal_in_expr(cx, closure_expr) + }, + + ExprKind::Block(blk, _) => blk.expr.as_ref().is_some_and(|e| has_ambiguous_literal_in_expr(cx, e)), + + ExprKind::If(cond, then_expr, else_expr) => { + has_ambiguous_literal_in_expr(cx, cond) + || has_ambiguous_literal_in_expr(cx, then_expr) + || else_expr.as_ref().is_some_and(|e| has_ambiguous_literal_in_expr(cx, e)) + }, + + ExprKind::Match(scrutinee, arms, _) => { + has_ambiguous_literal_in_expr(cx, scrutinee) + || arms.iter().any(|arm| has_ambiguous_literal_in_expr(cx, arm.body)) + }, + + ExprKind::Loop(body, ..) => body.expr.is_some_and(|e| has_ambiguous_literal_in_expr(cx, e)), + + ExprKind::Ret(opt_expr) | ExprKind::Break(_, opt_expr) => { + opt_expr.as_ref().is_some_and(|e| has_ambiguous_literal_in_expr(cx, e)) + }, + + _ => false, + } +} diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index c01f0ffaac9..2e03743e621 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -77,7 +77,8 @@ pub mod visitors; pub use self::attrs::*; pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match}; pub use self::hir_utils::{ - HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, + HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, has_ambiguous_literal_in_expr, hash_expr, + hash_stmt, is_bool, over, }; use core::mem; @@ -3497,3 +3498,64 @@ pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> false } } + +/// Checks if `expr` may be directly used as the return value of its enclosing body. +/// The following cases are covered: +/// - `expr` as the last expression of the body, or of a block that can be used as the return value +/// - `return expr` +/// - then or else part of a `if` in return position +/// - arm body of a `match` in a return position +/// - `break expr` or `break 'label expr` if the loop or block being exited is used as a return +/// value +/// +/// Contrary to [`TyCtxt::hir_get_fn_id_for_return_block()`], if `expr` is part of a +/// larger expression, for example a field expression of a `struct`, it will not be +/// considered as matching the condition and will return `false`. +/// +/// Also, even if `expr` is assigned to a variable which is later returned, this function +/// will still return `false` because `expr` is not used *directly* as the return value +/// as it goes through the intermediate variable. +pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let enclosing_body_owner = cx + .tcx + .local_def_id_to_hir_id(cx.tcx.hir_enclosing_body_owner(expr.hir_id)); + let mut prev_id = expr.hir_id; + let mut skip_until_id = None; + for (hir_id, node) in cx.tcx.hir_parent_iter(expr.hir_id) { + if hir_id == enclosing_body_owner { + return true; + } + if let Some(id) = skip_until_id { + prev_id = hir_id; + if id == hir_id { + skip_until_id = None; + } + continue; + } + match node { + Node::Block(Block { expr, .. }) if expr.is_some_and(|expr| expr.hir_id == prev_id) => {}, + Node::Arm(arm) if arm.body.hir_id == prev_id => {}, + Node::Expr(expr) => match expr.kind { + ExprKind::Ret(_) => return true, + ExprKind::If(_, then, opt_else) + if then.hir_id == prev_id || opt_else.is_some_and(|els| els.hir_id == prev_id) => {}, + ExprKind::Match(_, arms, _) if arms.iter().any(|arm| arm.hir_id == prev_id) => {}, + ExprKind::Block(block, _) if block.hir_id == prev_id => {}, + ExprKind::Break( + Destination { + target_id: Ok(target_id), + .. + }, + _, + ) => skip_until_id = Some(target_id), + _ => break, + }, + _ => break, + } + prev_id = hir_id; + } + + // `expr` is used as part of "something" and is not returned directly from its + // enclosing body. + false +} diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs index 7a0bef1a9bb..24ed4c3a8be 100644 --- a/src/tools/clippy/clippy_utils/src/msrvs.rs +++ b/src/tools/clippy/clippy_utils/src/msrvs.rs @@ -74,7 +74,7 @@ msrv_aliases! { 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF } 1,27,0 { ITERATOR_TRY_FOLD } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } - 1,24,0 { IS_ASCII_DIGIT } + 1,24,0 { IS_ASCII_DIGIT, PTR_NULL } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } 1,16,0 { STR_REPEAT } diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs index bffbcf073ab..fe208c032f4 100644 --- a/src/tools/clippy/clippy_utils/src/ty/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs @@ -889,7 +889,7 @@ impl AdtVariantInfo { .enumerate() .map(|(i, f)| (i, approx_ty_size(cx, f.ty(cx.tcx, subst)))) .collect::<Vec<_>>(); - fields_size.sort_by(|(_, a_size), (_, b_size)| (a_size.cmp(b_size))); + fields_size.sort_by(|(_, a_size), (_, b_size)| a_size.cmp(b_size)); Self { ind: i, @@ -898,7 +898,7 @@ impl AdtVariantInfo { } }) .collect::<Vec<_>>(); - variants_size.sort_by(|a, b| (b.size.cmp(&a.size))); + variants_size.sort_by(|a, b| b.size.cmp(&a.size)); variants_size } } diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index fc6e30a9804..ba5cbc73836 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -1,3 +1,5 @@ +use crate::msrvs::Msrv; +use crate::qualify_min_const_fn::is_stable_const_fn; use crate::ty::needs_ordered_drop; use crate::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; @@ -343,17 +345,17 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> .cx .qpath_res(p, hir_id) .opt_def_id() - .is_some_and(|id| self.cx.tcx.is_const_fn(id)) => {}, + .is_some_and(|id| is_stable_const_fn(self.cx, id, Msrv::default())) => {}, ExprKind::MethodCall(..) if self .cx .typeck_results() .type_dependent_def_id(e.hir_id) - .is_some_and(|id| self.cx.tcx.is_const_fn(id)) => {}, + .is_some_and(|id| is_stable_const_fn(self.cx, id, Msrv::default())) => {}, ExprKind::Binary(_, lhs, rhs) if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty() && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {}, - ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (), + ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_raw_ptr() => (), ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (), ExprKind::Index(base, _, _) if matches!( @@ -388,7 +390,8 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> | ExprKind::Repeat(..) | ExprKind::Struct(..) | ExprKind::Tup(_) - | ExprKind::Type(..) => (), + | ExprKind::Type(..) + | ExprKind::UnsafeBinderCast(..) => (), _ => { return ControlFlow::Break(()); @@ -676,10 +679,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( helper(typeck, true, else_expr, f)?; } }, - ExprKind::Type(e, _) => { - helper(typeck, consume, e, f)?; - }, - ExprKind::UnsafeBinderCast(_, e, _) => { + ExprKind::Type(e, _) | ExprKind::UnsafeBinderCast(_, e, _) => { helper(typeck, consume, e, f)?; }, diff --git a/src/tools/clippy/lintcheck/src/config.rs b/src/tools/clippy/lintcheck/src/config.rs index 83c3d7aba02..3b2ebf0c28a 100644 --- a/src/tools/clippy/lintcheck/src/config.rs +++ b/src/tools/clippy/lintcheck/src/config.rs @@ -68,6 +68,9 @@ pub(crate) enum Commands { /// This will limit the number of warnings that will be printed for each lint #[clap(long)] truncate: bool, + /// Write the diff summary to a JSON file if there are any changes + #[clap(long, value_name = "PATH")] + write_summary: Option<PathBuf>, }, /// Create a lintcheck crates TOML file containing the top N popular crates Popular { diff --git a/src/tools/clippy/lintcheck/src/json.rs b/src/tools/clippy/lintcheck/src/json.rs index 8ea0a41ed36..808997ff022 100644 --- a/src/tools/clippy/lintcheck/src/json.rs +++ b/src/tools/clippy/lintcheck/src/json.rs @@ -4,8 +4,8 @@ //! loading warnings from JSON files, and generating human-readable diffs //! between different linting runs. -use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::{fmt, fs}; use itertools::{EitherOrBoth, Itertools}; use serde::{Deserialize, Serialize}; @@ -17,7 +17,6 @@ const DEFAULT_LIMIT_PER_LINT: usize = 300; /// Target for total warnings to display across all lints when truncating output. const TRUNCATION_TOTAL_TARGET: usize = 1000; -/// Representation of a single Clippy warning for JSON serialization. #[derive(Debug, Deserialize, Serialize)] struct LintJson { /// The lint name e.g. `clippy::bytes_nth` @@ -29,7 +28,6 @@ struct LintJson { } impl LintJson { - /// Returns a tuple of name and `file_line` for sorting and comparison. fn key(&self) -> impl Ord + '_ { (self.name.as_str(), self.file_line.as_str()) } @@ -40,6 +38,57 @@ impl LintJson { } } +#[derive(Debug, Serialize)] +struct SummaryRow { + name: String, + added: usize, + removed: usize, + changed: usize, +} + +#[derive(Debug, Serialize)] +struct Summary(Vec<SummaryRow>); + +impl fmt::Display for Summary { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str( + "\ +| Lint | Added | Removed | Changed | +| ---- | ----: | ------: | ------: | +", + )?; + + for SummaryRow { + name, + added, + changed, + removed, + } in &self.0 + { + let html_id = to_html_id(name); + writeln!(f, "| [`{name}`](#{html_id}) | {added} | {changed} | {removed} |")?; + } + + Ok(()) + } +} + +impl Summary { + fn new(lints: &[LintWarnings]) -> Self { + Summary( + lints + .iter() + .map(|lint| SummaryRow { + name: lint.name.clone(), + added: lint.added.len(), + removed: lint.removed.len(), + changed: lint.changed.len(), + }) + .collect(), + ) + } +} + /// Creates the log file output for [`crate::config::OutputFormat::Json`] pub(crate) fn output(clippy_warnings: Vec<ClippyWarning>) -> String { let mut lints: Vec<LintJson> = clippy_warnings @@ -74,7 +123,7 @@ fn load_warnings(path: &Path) -> Vec<LintJson> { /// /// Compares warnings from `old_path` and `new_path`, then displays a summary table /// and detailed information about added, removed, and changed warnings. -pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) { +pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool, write_summary: Option<PathBuf>) { let old_warnings = load_warnings(old_path); let new_warnings = load_warnings(new_path); @@ -108,13 +157,16 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) { } } - print_summary_table(&lint_warnings); - println!(); - if lint_warnings.is_empty() { return; } + let summary = Summary::new(&lint_warnings); + if let Some(path) = write_summary { + let json = serde_json::to_string(&summary).unwrap(); + fs::write(path, json).unwrap(); + } + let truncate_after = if truncate { // Max 15 ensures that we at least have five messages per lint DEFAULT_LIMIT_PER_LINT @@ -126,6 +178,7 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) { usize::MAX }; + println!("{summary}"); for lint in lint_warnings { print_lint_warnings(&lint, truncate_after); } @@ -140,13 +193,11 @@ struct LintWarnings { changed: Vec<(LintJson, LintJson)>, } -/// Prints a formatted report for a single lint type with its warnings. fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) { let name = &lint.name; let html_id = to_html_id(name); - // The additional anchor is added for non GH viewers that don't prefix ID's - println!(r#"## `{name}` <a id="user-content-{html_id}"></a>"#); + println!(r#"<h2 id="{html_id}"><code>{name}</code></h2>"#); println!(); print!( @@ -162,22 +213,6 @@ fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) { print_changed_diff(&lint.changed, truncate_after / 3); } -/// Prints a summary table of all lints with counts of added, removed, and changed warnings. -fn print_summary_table(lints: &[LintWarnings]) { - println!("| Lint | Added | Removed | Changed |"); - println!("| ------------------------------------------ | ------: | ------: | ------: |"); - - for lint in lints { - println!( - "| {:<62} | {:>7} | {:>7} | {:>7} |", - format!("[`{}`](#user-content-{})", lint.name, to_html_id(&lint.name)), - lint.added.len(), - lint.removed.len(), - lint.changed.len() - ); - } -} - /// Prints a section of warnings with a header and formatted code blocks. fn print_warnings(title: &str, warnings: &[LintJson], truncate_after: usize) { if warnings.is_empty() { @@ -248,17 +283,16 @@ fn truncate<T>(list: &[T], truncate_after: usize) -> &[T] { } } -/// Prints a level 3 heading with an appropriate HTML ID for linking. fn print_h3(lint: &str, title: &str) { let html_id = to_html_id(lint); - // We have to use HTML here to be able to manually add an id. - println!(r#"### {title} <a id="user-content-{html_id}-{title}"></a>"#); + // We have to use HTML here to be able to manually add an id, GitHub doesn't add them automatically + println!(r#"<h3 id="{html_id}-{title}">{title}</h3>"#); } -/// GitHub's markdown parsers doesn't like IDs with `::` and `_`. This simplifies -/// the lint name for the HTML ID. +/// Creates a custom ID allowed by GitHub, they must start with `user-content-` and cannot contain +/// `::`/`_` fn to_html_id(lint_name: &str) -> String { - lint_name.replace("clippy::", "").replace('_', "-") + lint_name.replace("clippy::", "user-content-").replace('_', "-") } /// This generates the `x added` string for the start of the job summery. @@ -270,9 +304,6 @@ fn count_string(lint: &str, label: &str, count: usize) -> String { format!("0 {label}") } else { let html_id = to_html_id(lint); - // GitHub's job summaries don't add HTML ids to headings. That's why we - // manually have to add them. GitHub prefixes these manual ids with - // `user-content-` and that's how we end up with these awesome links :D - format!("[{count} {label}](#user-content-{html_id}-{label})") + format!("[{count} {label}](#{html_id}-{label})") } } diff --git a/src/tools/clippy/lintcheck/src/main.rs b/src/tools/clippy/lintcheck/src/main.rs index eb390eecbcc..3a60cfa79f4 100644 --- a/src/tools/clippy/lintcheck/src/main.rs +++ b/src/tools/clippy/lintcheck/src/main.rs @@ -303,7 +303,12 @@ fn main() { let config = LintcheckConfig::new(); match config.subcommand { - Some(Commands::Diff { old, new, truncate }) => json::diff(&old, &new, truncate), + Some(Commands::Diff { + old, + new, + truncate, + write_summary, + }) => json::diff(&old, &new, truncate, write_summary), Some(Commands::Popular { output, number }) => popular_crates::fetch(output, number).unwrap(), None => lintcheck(config), } diff --git a/src/tools/clippy/rust-toolchain.toml b/src/tools/clippy/rust-toolchain.toml index 124756a3600..f46e079db3f 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-06-26" +channel = "nightly-2025-07-10" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index cefe654fef6..aa786334711 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -16,8 +16,10 @@ use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; use ui_test::custom_flags::edition::Edition; use ui_test::custom_flags::rustfix::RustfixMode; +use ui_test::dependencies::DependencyBuilder; use ui_test::spanned::Spanned; -use ui_test::{Args, CommandBuilder, Config, Match, error_on_output_conflict, status_emitter}; +use ui_test::status_emitter::StatusEmitter; +use ui_test::{Args, CommandBuilder, Config, Match, error_on_output_conflict}; use std::collections::{BTreeMap, HashMap}; use std::env::{self, set_var, var_os}; @@ -27,46 +29,26 @@ use std::path::{Path, PathBuf}; use std::sync::mpsc::{Sender, channel}; use std::{fs, iter, thread}; -// Test dependencies may need an `extern crate` here to ensure that they show up -// in the depinfo file (otherwise cargo thinks they are unused) -extern crate futures; -extern crate if_chain; -extern crate itertools; -extern crate parking_lot; -extern crate quote; -extern crate syn; -extern crate tokio; - mod test_utils; -/// All crates used in UI tests are listed here -static TEST_DEPENDENCIES: &[&str] = &[ - "clippy_config", - "clippy_lints", - "clippy_utils", - "futures", - "if_chain", - "itertools", - "parking_lot", - "quote", - "regex", - "serde_derive", - "serde", - "syn", - "tokio", -]; - -/// Produces a string with an `--extern` flag for all UI test crate -/// dependencies. +/// All crates used in internal UI tests are listed here. +/// We directly re-use these crates from their normal clippy builds, so we don't have them +/// in `clippy_test_devs`. That saves a lot of time but also means they don't work in a stage 1 +/// test in rustc bootstrap. +static INTERNAL_TEST_DEPENDENCIES: &[&str] = &["clippy_config", "clippy_lints", "clippy_utils"]; + +/// Produces a string with an `--extern` flag for all `INTERNAL_TEST_DEPENDENCIES`. /// /// The dependency files are located by parsing the depinfo file for this test /// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test /// dependencies must be added to Cargo.toml at the project root. Test /// dependencies that are not *directly* used by this test module require an /// `extern crate` declaration. -fn extern_flags() -> Vec<String> { +fn internal_extern_flags() -> Vec<String> { + let current_exe_path = env::current_exe().unwrap(); + let deps_path = current_exe_path.parent().unwrap(); let current_exe_depinfo = { - let mut path = env::current_exe().unwrap(); + let mut path = current_exe_path.clone(); path.set_extension("d"); fs::read_to_string(path).unwrap() }; @@ -88,7 +70,7 @@ fn extern_flags() -> Vec<String> { Some((name, path_str)) }; if let Some((name, path)) = parse_name_path() - && TEST_DEPENDENCIES.contains(&name) + && INTERNAL_TEST_DEPENDENCIES.contains(&name) { // A dependency may be listed twice if it is available in sysroot, // and the sysroot dependencies are listed first. As of the writing, @@ -96,7 +78,7 @@ fn extern_flags() -> Vec<String> { crates.insert(name, path); } } - let not_found: Vec<&str> = TEST_DEPENDENCIES + let not_found: Vec<&str> = INTERNAL_TEST_DEPENDENCIES .iter() .copied() .filter(|n| !crates.contains_key(n)) @@ -111,6 +93,7 @@ fn extern_flags() -> Vec<String> { crates .into_iter() .map(|(name, path)| format!("--extern={name}={path}")) + .chain([format!("-Ldependency={}", deps_path.display())]) .collect() } @@ -119,7 +102,6 @@ const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal"); struct TestContext { args: Args, - extern_flags: Vec<String>, diagnostic_collector: Option<DiagnosticCollector>, collector_thread: Option<thread::JoinHandle<()>>, } @@ -134,7 +116,6 @@ impl TestContext { .unzip(); Self { args, - extern_flags: extern_flags(), diagnostic_collector, collector_thread, } @@ -158,6 +139,15 @@ impl TestContext { }; let defaults = config.comment_defaults.base(); defaults.set_custom("edition", Edition("2024".into())); + defaults.set_custom( + "dependencies", + DependencyBuilder { + program: CommandBuilder::cargo(), + crate_manifest_path: Path::new("clippy_test_deps").join("Cargo.toml"), + build_std: None, + bless_lockfile: self.args.bless, + }, + ); defaults.exit_status = None.into(); if mandatory_annotations { defaults.require_annotations = Some(Spanned::dummy(true)).into(); @@ -182,12 +172,10 @@ impl TestContext { "-Zui-testing", "-Zdeduplicate-diagnostics=no", "-Dwarnings", - &format!("-Ldependency={}", deps_path.display()), ] .map(OsString::from), ); - config.program.args.extend(self.extern_flags.iter().map(OsString::from)); // Prevent rustc from creating `rustc-ice-*` files the console output is enough. config.program.envs.push(("RUSTC_ICE".into(), Some("0".into()))); @@ -217,7 +205,7 @@ fn run_ui(cx: &TestContext) { vec![config], ui_test::default_file_filter, ui_test::default_per_file_config, - status_emitter::Text::from(cx.args.format), + Box::<dyn StatusEmitter>::from(cx.args.format), ) .unwrap(); } @@ -227,13 +215,17 @@ fn run_internal_tests(cx: &TestContext) { return; } let mut config = cx.base_config("ui-internal", true); + config + .program + .args + .extend(internal_extern_flags().iter().map(OsString::from)); config.bless_command = Some("cargo uitest --features internal -- -- --bless".into()); ui_test::run_tests_generic( vec![config], ui_test::default_file_filter, ui_test::default_per_file_config, - status_emitter::Text::from(cx.args.format), + Box::<dyn StatusEmitter>::from(cx.args.format), ) .unwrap(); } @@ -257,7 +249,7 @@ fn run_ui_toml(cx: &TestContext) { .envs .push(("CLIPPY_CONF_DIR".into(), Some(path.parent().unwrap().into()))); }, - status_emitter::Text::from(cx.args.format), + Box::<dyn StatusEmitter>::from(cx.args.format), ) .unwrap(); } @@ -304,7 +296,7 @@ fn run_ui_cargo(cx: &TestContext) { .then(|| ui_test::default_any_file_filter(path, config) && !ignored_32bit(path)) }, |_config, _file_contents| {}, - status_emitter::Text::from(cx.args.format), + Box::<dyn StatusEmitter>::from(cx.args.format), ) .unwrap(); } diff --git a/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr b/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr index 20cdff5fcd1..cebfc48a884 100644 --- a/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr +++ b/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr @@ -450,5 +450,13 @@ help: consider removing the safety comment LL | // SAFETY: unnecessary_safety_comment triggers here | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 52 previous errors +error: unsafe block missing a safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:733:12 + | +LL | return unsafe { h() }; + | ^^^^^^^^^^^^^^ + | + = help: consider adding a safety comment on the preceding line + +error: aborting due to 53 previous errors diff --git a/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs b/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs index 91a02bc3d7c..a2d7c1b6c79 100644 --- a/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs +++ b/src/tools/clippy/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs @@ -723,4 +723,15 @@ fn issue_13039() { _ = unsafe { foo() } } +fn rfl_issue15034() -> i32 { + unsafe fn h() -> i32 { + 1i32 + } + // This shouldn't lint with accept-comment-above-attributes! Thus fixing a false positive! + // SAFETY: My safety comment! + #[allow(clippy::unnecessary_cast)] + return unsafe { h() }; + //~[disabled]^ ERROR: unsafe block missing a safety comment +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/borrow_as_ptr.fixed b/src/tools/clippy/tests/ui/borrow_as_ptr.fixed index 3ba2eea59f0..3f6e5245b87 100644 --- a/src/tools/clippy/tests/ui/borrow_as_ptr.fixed +++ b/src/tools/clippy/tests/ui/borrow_as_ptr.fixed @@ -47,3 +47,9 @@ fn implicit_cast() { // Do not lint references to temporaries core::ptr::eq(&0i32, &1i32); } + +fn issue_15141() { + let a = String::new(); + // Don't lint cast to dyn trait pointers + let b = &a as *const dyn std::any::Any; +} diff --git a/src/tools/clippy/tests/ui/borrow_as_ptr.rs b/src/tools/clippy/tests/ui/borrow_as_ptr.rs index 8cdd0512da5..20f4f40e001 100644 --- a/src/tools/clippy/tests/ui/borrow_as_ptr.rs +++ b/src/tools/clippy/tests/ui/borrow_as_ptr.rs @@ -47,3 +47,9 @@ fn implicit_cast() { // Do not lint references to temporaries core::ptr::eq(&0i32, &1i32); } + +fn issue_15141() { + let a = String::new(); + // Don't lint cast to dyn trait pointers + let b = &a as *const dyn std::any::Any; +} diff --git a/src/tools/clippy/tests/ui/cast_size.32bit.stderr b/src/tools/clippy/tests/ui/cast_size.32bit.stderr index cb1620e36a2..5811cb3607b 100644 --- a/src/tools/clippy/tests/ui/cast_size.32bit.stderr +++ b/src/tools/clippy/tests/ui/cast_size.32bit.stderr @@ -177,6 +177,14 @@ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bi LL | 9_999_999_999_999_999usize as f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: casting `usize` to `u16` may truncate the value + --> tests/ui/cast_size.rs:71:20 + | +LL | const N: u16 = M as u16; + | ^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... + error: literal out of range for `usize` --> tests/ui/cast_size.rs:63:5 | @@ -186,5 +194,5 @@ LL | 9_999_999_999_999_999usize as f64; = note: the literal `9_999_999_999_999_999usize` does not fit into the type `usize` whose range is `0..=4294967295` = note: `#[deny(overflowing_literals)]` on by default -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors diff --git a/src/tools/clippy/tests/ui/cast_size.64bit.stderr b/src/tools/clippy/tests/ui/cast_size.64bit.stderr index b6000a52abb..ba1419583ae 100644 --- a/src/tools/clippy/tests/ui/cast_size.64bit.stderr +++ b/src/tools/clippy/tests/ui/cast_size.64bit.stderr @@ -177,5 +177,13 @@ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bi LL | 9_999_999_999_999_999usize as f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 18 previous errors +error: casting `usize` to `u16` may truncate the value + --> tests/ui/cast_size.rs:71:20 + | +LL | const N: u16 = M as u16; + | ^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... + +error: aborting due to 19 previous errors diff --git a/src/tools/clippy/tests/ui/cast_size.rs b/src/tools/clippy/tests/ui/cast_size.rs index e5bef2a99d5..ecc58669419 100644 --- a/src/tools/clippy/tests/ui/cast_size.rs +++ b/src/tools/clippy/tests/ui/cast_size.rs @@ -65,3 +65,9 @@ fn main() { //~[32bit]^^ ERROR: literal out of range for `usize` // 999_999_999_999_999_999_999_999_999_999u128 as f128; } + +fn issue15163() { + const M: usize = 100; + const N: u16 = M as u16; + //~^ cast_possible_truncation +} diff --git a/src/tools/clippy/tests/ui/coerce_container_to_any.fixed b/src/tools/clippy/tests/ui/coerce_container_to_any.fixed index ae9d3ef9656..b5b3f15b4de 100644 --- a/src/tools/clippy/tests/ui/coerce_container_to_any.fixed +++ b/src/tools/clippy/tests/ui/coerce_container_to_any.fixed @@ -3,7 +3,7 @@ use std::any::Any; fn main() { - let x: Box<dyn Any> = Box::new(()); + let mut x: Box<dyn Any> = Box::new(()); let ref_x = &x; f(&*x); @@ -15,12 +15,23 @@ fn main() { let _: &dyn Any = &*x; //~^ coerce_container_to_any + let _: &dyn Any = &*x; + //~^ coerce_container_to_any + + let _: &mut dyn Any = &mut *x; + //~^ coerce_container_to_any + f(&42); f(&Box::new(())); f(&Box::new(Box::new(()))); + let ref_x = &x; f(&**ref_x); f(&*x); let _: &dyn Any = &*x; + + // https://github.com/rust-lang/rust-clippy/issues/15045 + #[allow(clippy::needless_borrow)] + (&x).downcast_ref::<()>().unwrap(); } fn f(_: &dyn Any) {} diff --git a/src/tools/clippy/tests/ui/coerce_container_to_any.rs b/src/tools/clippy/tests/ui/coerce_container_to_any.rs index 9948bd48e0d..4d6527bb552 100644 --- a/src/tools/clippy/tests/ui/coerce_container_to_any.rs +++ b/src/tools/clippy/tests/ui/coerce_container_to_any.rs @@ -3,7 +3,7 @@ use std::any::Any; fn main() { - let x: Box<dyn Any> = Box::new(()); + let mut x: Box<dyn Any> = Box::new(()); let ref_x = &x; f(&x); @@ -15,12 +15,23 @@ fn main() { let _: &dyn Any = &x; //~^ coerce_container_to_any + let _: &dyn Any = &mut x; + //~^ coerce_container_to_any + + let _: &mut dyn Any = &mut x; + //~^ coerce_container_to_any + f(&42); f(&Box::new(())); f(&Box::new(Box::new(()))); + let ref_x = &x; f(&**ref_x); f(&*x); let _: &dyn Any = &*x; + + // https://github.com/rust-lang/rust-clippy/issues/15045 + #[allow(clippy::needless_borrow)] + (&x).downcast_ref::<()>().unwrap(); } fn f(_: &dyn Any) {} diff --git a/src/tools/clippy/tests/ui/coerce_container_to_any.stderr b/src/tools/clippy/tests/ui/coerce_container_to_any.stderr index 00ab77e0ce0..26389c9186e 100644 --- a/src/tools/clippy/tests/ui/coerce_container_to_any.stderr +++ b/src/tools/clippy/tests/ui/coerce_container_to_any.stderr @@ -19,5 +19,17 @@ error: coercing `&std::boxed::Box<dyn std::any::Any>` to `&dyn Any` LL | let _: &dyn Any = &x; | ^^ help: consider dereferencing: `&*x` -error: aborting due to 3 previous errors +error: coercing `&mut std::boxed::Box<dyn std::any::Any>` to `&dyn Any` + --> tests/ui/coerce_container_to_any.rs:18:23 + | +LL | let _: &dyn Any = &mut x; + | ^^^^^^ help: consider dereferencing: `&*x` + +error: coercing `&mut std::boxed::Box<dyn std::any::Any>` to `&mut dyn Any` + --> tests/ui/coerce_container_to_any.rs:21:27 + | +LL | let _: &mut dyn Any = &mut x; + | ^^^^^^ help: consider dereferencing: `&mut *x` + +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/disallowed_script_idents.rs b/src/tools/clippy/tests/ui/disallowed_script_idents.rs index 08fd1d9669e..dae380045ae 100644 --- a/src/tools/clippy/tests/ui/disallowed_script_idents.rs +++ b/src/tools/clippy/tests/ui/disallowed_script_idents.rs @@ -15,3 +15,17 @@ fn main() { let カウンタ = 10; //~^ disallowed_script_idents } + +fn issue15116() { + const ÄÖÜ: u8 = 0; + const _ÄÖÜ: u8 = 0; + const Ä_ÖÜ: u8 = 0; + const ÄÖ_Ü: u8 = 0; + const ÄÖÜ_: u8 = 0; + let äöüß = 1; + let _äöüß = 1; + let ä_öüß = 1; + let äö_üß = 1; + let äöü_ß = 1; + let äöüß_ = 1; +} diff --git a/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.fixed b/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.fixed index 065f4486e39..5c57c58fbc0 100644 --- a/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.fixed +++ b/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.fixed @@ -72,8 +72,6 @@ pub struct NotEmptyTight; /// ## Heading /// -/// - [x][] - Done -//~^ ERROR: link reference defined in list item -/// - [ ][] - Not Done -//~^ ERROR: link reference defined in list item +/// - [x] - Done +/// - [ ] - Not Done pub struct GithubCheckboxes; diff --git a/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.rs b/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.rs index c7eab50c8b3..06b6ba49e19 100644 --- a/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.rs +++ b/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.rs @@ -73,7 +73,5 @@ pub struct NotEmptyTight; /// ## Heading /// /// - [x] - Done -//~^ ERROR: link reference defined in list item /// - [ ] - Not Done -//~^ ERROR: link reference defined in list item pub struct GithubCheckboxes; diff --git a/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.stderr b/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.stderr index 5a815dabf4d..27314c7e968 100644 --- a/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.stderr +++ b/src/tools/clippy/tests/ui/doc/doc_nested_refdef_list_item.stderr @@ -144,29 +144,5 @@ help: for an intra-doc link, add `[]` between the label and the colon LL | /// - [link][]: def "title" | ++ -error: link reference defined in list item - --> tests/ui/doc/doc_nested_refdef_list_item.rs:75:7 - | -LL | /// - [x] - Done - | ^^^ - | - = help: link definitions are not shown in rendered documentation -help: for an intra-doc link, add `[]` between the label and the colon - | -LL | /// - [x][] - Done - | ++ - -error: link reference defined in list item - --> tests/ui/doc/doc_nested_refdef_list_item.rs:77:7 - | -LL | /// - [ ] - Not Done - | ^^^ - | - = help: link definitions are not shown in rendered documentation -help: for an intra-doc link, add `[]` between the label and the colon - | -LL | /// - [ ][] - Not Done - | ++ - -error: aborting due to 14 previous errors +error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/empty_loop_intrinsic.rs b/src/tools/clippy/tests/ui/empty_loop_intrinsic.rs new file mode 100644 index 00000000000..a550e560965 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop_intrinsic.rs @@ -0,0 +1,13 @@ +//@check-pass + +#![warn(clippy::empty_loop)] +#![feature(intrinsics)] +#![feature(rustc_attrs)] + +// From issue #15200 +#[rustc_intrinsic] +#[rustc_nounwind] +/// # Safety +pub const unsafe fn simd_insert<T, U>(x: T, idx: u32, val: U) -> T; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/exit1_compile_flag_test.rs b/src/tools/clippy/tests/ui/exit1_compile_flag_test.rs new file mode 100644 index 00000000000..9f83ed32533 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit1_compile_flag_test.rs @@ -0,0 +1,17 @@ +//@compile-flags: --test +#![warn(clippy::exit)] + +fn not_main() { + if true { + std::process::exit(4); + //~^ exit + } +} + +fn main() { + if true { + std::process::exit(2); + }; + not_main(); + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit1_compile_flag_test.stderr b/src/tools/clippy/tests/ui/exit1_compile_flag_test.stderr new file mode 100644 index 00000000000..6e33c39f0d7 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit1_compile_flag_test.stderr @@ -0,0 +1,11 @@ +error: usage of `process::exit` + --> tests/ui/exit1_compile_flag_test.rs:6:9 + | +LL | std::process::exit(4); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::exit)]` + +error: aborting due to 1 previous error + diff --git a/src/tools/clippy/tests/ui/exit2_compile_flag_test.rs b/src/tools/clippy/tests/ui/exit2_compile_flag_test.rs new file mode 100644 index 00000000000..0b994ebc56c --- /dev/null +++ b/src/tools/clippy/tests/ui/exit2_compile_flag_test.rs @@ -0,0 +1,15 @@ +//@compile-flags: --test +#![warn(clippy::exit)] + +fn also_not_main() { + std::process::exit(3); + //~^ exit +} + +fn main() { + if true { + std::process::exit(2); + }; + also_not_main(); + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit2_compile_flag_test.stderr b/src/tools/clippy/tests/ui/exit2_compile_flag_test.stderr new file mode 100644 index 00000000000..51eb26e9c2a --- /dev/null +++ b/src/tools/clippy/tests/ui/exit2_compile_flag_test.stderr @@ -0,0 +1,11 @@ +error: usage of `process::exit` + --> tests/ui/exit2_compile_flag_test.rs:5:5 + | +LL | std::process::exit(3); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::exit)]` + +error: aborting due to 1 previous error + diff --git a/src/tools/clippy/tests/ui/exit3_compile_flag_test.rs b/src/tools/clippy/tests/ui/exit3_compile_flag_test.rs new file mode 100644 index 00000000000..f8131ead2da --- /dev/null +++ b/src/tools/clippy/tests/ui/exit3_compile_flag_test.rs @@ -0,0 +1,11 @@ +//@ check-pass +//@compile-flags: --test + +#![warn(clippy::exit)] + +fn main() { + if true { + std::process::exit(2); + }; + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit4.rs b/src/tools/clippy/tests/ui/exit4.rs new file mode 100644 index 00000000000..821a26fd78b --- /dev/null +++ b/src/tools/clippy/tests/ui/exit4.rs @@ -0,0 +1,8 @@ +//@ check-pass +//@compile-flags: --test + +#![warn(clippy::exit)] + +fn main() { + std::process::exit(0) +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.fixed b/src/tools/clippy/tests/ui/floating_point_mul_add.fixed index 83aeddb2a1f..884bae00432 100644 --- a/src/tools/clippy/tests/ui/floating_point_mul_add.fixed +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.fixed @@ -69,3 +69,47 @@ fn _issue11831() { let _ = a + b * c; } + +fn _issue14897() { + let x = 1.0; + let _ = x * 2.0 + 0.5; // should not suggest mul_add + let _ = 0.5 + x * 2.0; // should not suggest mul_add + let _ = 0.5 + x * 1.2; // should not suggest mul_add + let _ = 1.2 + x * 1.2; // should not suggest mul_add + + let x = -1.0; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = { 4.0 }; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = if 1 > 2 { 1.0 } else { 2.0 }; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = 2.4 + 1.2; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let f = || 4.0; + let x = f(); + let _ = 0.5 + f() * 1.2; // should not suggest mul_add + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = 0.1; + let y = x; + let z = y; + let _ = 0.5 + z * 1.2; // should not suggest mul_add + + let _ = 2.0f64.mul_add(x, 0.5); + //~^ suboptimal_flops + let _ = 2.0f64.mul_add(x, 0.5); + //~^ suboptimal_flops + + let _ = 2.0f64.mul_add(4.0, x); + //~^ suboptimal_flops + + let y: f64 = 1.0; + let _ = y.mul_add(2.0, 0.5); + //~^ suboptimal_flops + let _ = 1.0f64.mul_add(2.0, 0.5); + //~^ suboptimal_flops +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.rs b/src/tools/clippy/tests/ui/floating_point_mul_add.rs index 039ee8d053f..9ceb2ec9606 100644 --- a/src/tools/clippy/tests/ui/floating_point_mul_add.rs +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.rs @@ -69,3 +69,47 @@ fn _issue11831() { let _ = a + b * c; } + +fn _issue14897() { + let x = 1.0; + let _ = x * 2.0 + 0.5; // should not suggest mul_add + let _ = 0.5 + x * 2.0; // should not suggest mul_add + let _ = 0.5 + x * 1.2; // should not suggest mul_add + let _ = 1.2 + x * 1.2; // should not suggest mul_add + + let x = -1.0; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = { 4.0 }; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = if 1 > 2 { 1.0 } else { 2.0 }; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = 2.4 + 1.2; + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let f = || 4.0; + let x = f(); + let _ = 0.5 + f() * 1.2; // should not suggest mul_add + let _ = 0.5 + x * 1.2; // should not suggest mul_add + + let x = 0.1; + let y = x; + let z = y; + let _ = 0.5 + z * 1.2; // should not suggest mul_add + + let _ = 0.5 + 2.0 * x; + //~^ suboptimal_flops + let _ = 2.0 * x + 0.5; + //~^ suboptimal_flops + + let _ = x + 2.0 * 4.0; + //~^ suboptimal_flops + + let y: f64 = 1.0; + let _ = y * 2.0 + 0.5; + //~^ suboptimal_flops + let _ = 1.0 * 2.0 + 0.5; + //~^ suboptimal_flops +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.stderr b/src/tools/clippy/tests/ui/floating_point_mul_add.stderr index 6482127bcc0..dad65ddf2ec 100644 --- a/src/tools/clippy/tests/ui/floating_point_mul_add.stderr +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.stderr @@ -79,5 +79,35 @@ error: multiply and add expressions can be calculated more efficiently and accur LL | let _ = a - (b * u as f64); | ^^^^^^^^^^^^^^^^^^ help: consider using: `b.mul_add(-(u as f64), a)` -error: aborting due to 13 previous errors +error: multiply and add expressions can be calculated more efficiently and accurately + --> tests/ui/floating_point_mul_add.rs:102:13 + | +LL | let _ = 0.5 + 2.0 * x; + | ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(x, 0.5)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> tests/ui/floating_point_mul_add.rs:104:13 + | +LL | let _ = 2.0 * x + 0.5; + | ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(x, 0.5)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> tests/ui/floating_point_mul_add.rs:107:13 + | +LL | let _ = x + 2.0 * 4.0; + | ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4.0, x)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> tests/ui/floating_point_mul_add.rs:111:13 + | +LL | let _ = y * 2.0 + 0.5; + | ^^^^^^^^^^^^^ help: consider using: `y.mul_add(2.0, 0.5)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> tests/ui/floating_point_mul_add.rs:113:13 + | +LL | let _ = 1.0 * 2.0 + 0.5; + | ^^^^^^^^^^^^^^^ help: consider using: `1.0f64.mul_add(2.0, 0.5)` + +error: aborting due to 18 previous errors diff --git a/src/tools/clippy/tests/ui/manual_is_variant_and.fixed b/src/tools/clippy/tests/ui/manual_is_variant_and.fixed index 6425f32c09c..65a9cfa6e64 100644 --- a/src/tools/clippy/tests/ui/manual_is_variant_and.fixed +++ b/src/tools/clippy/tests/ui/manual_is_variant_and.fixed @@ -61,7 +61,7 @@ fn option_methods() { let _ = Some(2).is_some_and(|x| x % 2 == 0); //~^ manual_is_variant_and - let _ = Some(2).is_none_or(|x| x % 2 == 0); + let _ = Some(2).is_none_or(|x| x % 2 != 0); //~^ manual_is_variant_and let _ = Some(2).is_some_and(|x| x % 2 == 0); //~^ manual_is_variant_and @@ -116,3 +116,113 @@ fn main() { option_methods(); result_methods(); } + +fn issue15202() { + let xs = [None, Some(b'_'), Some(b'1')]; + for x in xs { + let a1 = x.is_none_or(|b| !b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = x.is_none_or(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.is_none_or(|b| b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = x.is_none_or(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.is_some_and(|b| b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = x.is_some_and(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.is_some_and(|b| !b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = x.is_some_and(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + let xs = [Err("foo"), Ok(b'_'), Ok(b'1')]; + for x in xs { + let a1 = !x.is_ok_and(|b| b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = !x.is_ok_and(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = !x.is_ok_and(|b| !b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = !x.is_ok_and(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.is_ok_and(|b| b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = x.is_ok_and(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.is_ok_and(|b| !b.is_ascii_digit()); + //~^ manual_is_variant_and + let a2 = x.is_ok_and(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } +} + +mod with_func { + fn iad(b: u8) -> bool { + b.is_ascii_digit() + } + + fn check_option(b: Option<u8>) { + let a1 = b.is_some_and(iad); + //~^ manual_is_variant_and + let a2 = b.is_some_and(iad); + assert_eq!(a1, a2); + + let a1 = b.is_some_and(|x| !iad(x)); + //~^ manual_is_variant_and + let a2 = b.is_some_and(|x| !iad(x)); + assert_eq!(a1, a2); + + let a1 = b.is_none_or(|x| !iad(x)); + //~^ manual_is_variant_and + let a2 = b.is_none_or(|x| !iad(x)); + assert_eq!(a1, a2); + + let a1 = b.is_none_or(iad); + //~^ manual_is_variant_and + let a2 = b.is_none_or(iad); + assert_eq!(a1, a2); + } + + fn check_result(b: Result<u8, ()>) { + let a1 = b.is_ok_and(iad); + //~^ manual_is_variant_and + let a2 = b.is_ok_and(iad); + assert_eq!(a1, a2); + + let a1 = b.is_ok_and(|x| !iad(x)); + //~^ manual_is_variant_and + let a2 = b.is_ok_and(|x| !iad(x)); + assert_eq!(a1, a2); + + let a1 = !b.is_ok_and(iad); + //~^ manual_is_variant_and + let a2 = !b.is_ok_and(iad); + assert_eq!(a1, a2); + + let a1 = !b.is_ok_and(|x| !iad(x)); + //~^ manual_is_variant_and + let a2 = !b.is_ok_and(|x| !iad(x)); + assert_eq!(a1, a2); + } +} diff --git a/src/tools/clippy/tests/ui/manual_is_variant_and.rs b/src/tools/clippy/tests/ui/manual_is_variant_and.rs index e069e97a04d..85b45d654a7 100644 --- a/src/tools/clippy/tests/ui/manual_is_variant_and.rs +++ b/src/tools/clippy/tests/ui/manual_is_variant_and.rs @@ -125,3 +125,113 @@ fn main() { option_methods(); result_methods(); } + +fn issue15202() { + let xs = [None, Some(b'_'), Some(b'1')]; + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) != Some(true); + //~^ manual_is_variant_and + let a2 = x.is_none_or(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) != Some(false); + //~^ manual_is_variant_and + let a2 = x.is_none_or(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) == Some(true); + //~^ manual_is_variant_and + let a2 = x.is_some_and(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) == Some(false); + //~^ manual_is_variant_and + let a2 = x.is_some_and(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + let xs = [Err("foo"), Ok(b'_'), Ok(b'1')]; + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) != Ok(true); + //~^ manual_is_variant_and + let a2 = !x.is_ok_and(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) != Ok(false); + //~^ manual_is_variant_and + let a2 = !x.is_ok_and(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) == Ok(true); + //~^ manual_is_variant_and + let a2 = x.is_ok_and(|b| b.is_ascii_digit()); + assert_eq!(a1, a2); + } + + for x in xs { + let a1 = x.map(|b| b.is_ascii_digit()) == Ok(false); + //~^ manual_is_variant_and + let a2 = x.is_ok_and(|b| !b.is_ascii_digit()); + assert_eq!(a1, a2); + } +} + +mod with_func { + fn iad(b: u8) -> bool { + b.is_ascii_digit() + } + + fn check_option(b: Option<u8>) { + let a1 = b.map(iad) == Some(true); + //~^ manual_is_variant_and + let a2 = b.is_some_and(iad); + assert_eq!(a1, a2); + + let a1 = b.map(iad) == Some(false); + //~^ manual_is_variant_and + let a2 = b.is_some_and(|x| !iad(x)); + assert_eq!(a1, a2); + + let a1 = b.map(iad) != Some(true); + //~^ manual_is_variant_and + let a2 = b.is_none_or(|x| !iad(x)); + assert_eq!(a1, a2); + + let a1 = b.map(iad) != Some(false); + //~^ manual_is_variant_and + let a2 = b.is_none_or(iad); + assert_eq!(a1, a2); + } + + fn check_result(b: Result<u8, ()>) { + let a1 = b.map(iad) == Ok(true); + //~^ manual_is_variant_and + let a2 = b.is_ok_and(iad); + assert_eq!(a1, a2); + + let a1 = b.map(iad) == Ok(false); + //~^ manual_is_variant_and + let a2 = b.is_ok_and(|x| !iad(x)); + assert_eq!(a1, a2); + + let a1 = b.map(iad) != Ok(true); + //~^ manual_is_variant_and + let a2 = !b.is_ok_and(iad); + assert_eq!(a1, a2); + + let a1 = b.map(iad) != Ok(false); + //~^ manual_is_variant_and + let a2 = !b.is_ok_and(|x| !iad(x)); + assert_eq!(a1, a2); + } +} diff --git a/src/tools/clippy/tests/ui/manual_is_variant_and.stderr b/src/tools/clippy/tests/ui/manual_is_variant_and.stderr index f770319a268..da36b5a07d2 100644 --- a/src/tools/clippy/tests/ui/manual_is_variant_and.stderr +++ b/src/tools/clippy/tests/ui/manual_is_variant_and.stderr @@ -54,7 +54,7 @@ error: called `.map() != Some()` --> tests/ui/manual_is_variant_and.rs:70:13 | LL | let _ = Some(2).map(|x| x % 2 == 0) != Some(true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Some(2).is_none_or(|x| x % 2 == 0)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Some(2).is_none_or(|x| x % 2 != 0)` error: called `.map() == Some()` --> tests/ui/manual_is_variant_and.rs:72:13 @@ -126,5 +126,101 @@ error: called `map(<f>).unwrap_or_default()` on a `Result` value LL | let _ = res2.map(char::is_alphanumeric).unwrap_or_default(); // should lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_ok_and(char::is_alphanumeric)` -error: aborting due to 15 previous errors +error: called `.map() != Some()` + --> tests/ui/manual_is_variant_and.rs:132:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) != Some(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_none_or(|b| !b.is_ascii_digit())` + +error: called `.map() != Some()` + --> tests/ui/manual_is_variant_and.rs:139:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) != Some(false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_none_or(|b| b.is_ascii_digit())` + +error: called `.map() == Some()` + --> tests/ui/manual_is_variant_and.rs:146:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) == Some(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_some_and(|b| b.is_ascii_digit())` + +error: called `.map() == Some()` + --> tests/ui/manual_is_variant_and.rs:153:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) == Some(false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_some_and(|b| !b.is_ascii_digit())` + +error: called `.map() != Ok()` + --> tests/ui/manual_is_variant_and.rs:161:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) != Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!x.is_ok_and(|b| b.is_ascii_digit())` + +error: called `.map() != Ok()` + --> tests/ui/manual_is_variant_and.rs:168:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) != Ok(false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!x.is_ok_and(|b| !b.is_ascii_digit())` + +error: called `.map() == Ok()` + --> tests/ui/manual_is_variant_and.rs:175:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) == Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_ok_and(|b| b.is_ascii_digit())` + +error: called `.map() == Ok()` + --> tests/ui/manual_is_variant_and.rs:182:18 + | +LL | let a1 = x.map(|b| b.is_ascii_digit()) == Ok(false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_ok_and(|b| !b.is_ascii_digit())` + +error: called `.map() == Some()` + --> tests/ui/manual_is_variant_and.rs:195:18 + | +LL | let a1 = b.map(iad) == Some(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_some_and(iad)` + +error: called `.map() == Some()` + --> tests/ui/manual_is_variant_and.rs:200:18 + | +LL | let a1 = b.map(iad) == Some(false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_some_and(|x| !iad(x))` + +error: called `.map() != Some()` + --> tests/ui/manual_is_variant_and.rs:205:18 + | +LL | let a1 = b.map(iad) != Some(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_none_or(|x| !iad(x))` + +error: called `.map() != Some()` + --> tests/ui/manual_is_variant_and.rs:210:18 + | +LL | let a1 = b.map(iad) != Some(false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_none_or(iad)` + +error: called `.map() == Ok()` + --> tests/ui/manual_is_variant_and.rs:217:18 + | +LL | let a1 = b.map(iad) == Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_ok_and(iad)` + +error: called `.map() == Ok()` + --> tests/ui/manual_is_variant_and.rs:222:18 + | +LL | let a1 = b.map(iad) == Ok(false); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_ok_and(|x| !iad(x))` + +error: called `.map() != Ok()` + --> tests/ui/manual_is_variant_and.rs:227:18 + | +LL | let a1 = b.map(iad) != Ok(true); + | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `!b.is_ok_and(iad)` + +error: called `.map() != Ok()` + --> tests/ui/manual_is_variant_and.rs:232:18 + | +LL | let a1 = b.map(iad) != Ok(false); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!b.is_ok_and(|x| !iad(x))` + +error: aborting due to 31 previous errors diff --git a/src/tools/clippy/tests/ui/manual_let_else_match.fixed b/src/tools/clippy/tests/ui/manual_let_else_match.fixed index 588ba5edd8f..15f604aec29 100644 --- a/src/tools/clippy/tests/ui/manual_let_else_match.fixed +++ b/src/tools/clippy/tests/ui/manual_let_else_match.fixed @@ -137,3 +137,48 @@ fn not_fire() { fn issue11579() { let Some(msg) = Some("hi") else { unreachable!("can't happen") }; } + +#[derive(Clone, Copy)] +struct Issue9939<T> { + avalanche: T, +} + +fn issue9939() { + let issue = Some(Issue9939 { avalanche: 1 }); + let Some(Issue9939 { avalanche: tornado }) = issue else { unreachable!("can't happen") }; + let issue = Some(Issue9939 { avalanche: true }); + let Some(Issue9939 { avalanche: acid_rain }) = issue else { unreachable!("can't happen") }; + assert_eq!(tornado, 1); + assert!(acid_rain); + + // without shadowing + let _x @ Some(Issue9939 { avalanche: _y }) = issue else { unreachable!("can't happen") }; + + // with shadowing + let Some(Issue9939 { avalanche: _x }) = issue else { unreachable!("can't happen") }; +} + +#[derive(Clone, Copy)] +struct Issue9939b<T, U> { + earthquake: T, + hurricane: U, +} + +fn issue9939b() { + let issue = Some(Issue9939b { + earthquake: true, + hurricane: 1, + }); + let issue @ Some(Issue9939b { earthquake: flood, hurricane: drought }) = issue else { unreachable!("can't happen") }; + assert_eq!(drought, 1); + assert!(flood); + assert!(issue.is_some()); + + // without shadowing + let _x @ Some(Issue9939b { earthquake: erosion, hurricane: _y }) = issue else { unreachable!("can't happen") }; + assert!(erosion); + + // with shadowing + let Some(Issue9939b { earthquake: erosion, hurricane: _x }) = issue else { unreachable!("can't happen") }; + assert!(erosion); +} diff --git a/src/tools/clippy/tests/ui/manual_let_else_match.rs b/src/tools/clippy/tests/ui/manual_let_else_match.rs index 6416753bac1..44a044b142b 100644 --- a/src/tools/clippy/tests/ui/manual_let_else_match.rs +++ b/src/tools/clippy/tests/ui/manual_let_else_match.rs @@ -177,3 +177,76 @@ fn issue11579() { _ => unreachable!("can't happen"), }; } + +#[derive(Clone, Copy)] +struct Issue9939<T> { + avalanche: T, +} + +fn issue9939() { + let issue = Some(Issue9939 { avalanche: 1 }); + let tornado = match issue { + //~^ manual_let_else + Some(Issue9939 { avalanche }) => avalanche, + _ => unreachable!("can't happen"), + }; + let issue = Some(Issue9939 { avalanche: true }); + let acid_rain = match issue { + //~^ manual_let_else + Some(Issue9939 { avalanche: tornado }) => tornado, + _ => unreachable!("can't happen"), + }; + assert_eq!(tornado, 1); + assert!(acid_rain); + + // without shadowing + let _y = match issue { + //~^ manual_let_else + _x @ Some(Issue9939 { avalanche }) => avalanche, + None => unreachable!("can't happen"), + }; + + // with shadowing + let _x = match issue { + //~^ manual_let_else + _x @ Some(Issue9939 { avalanche }) => avalanche, + None => unreachable!("can't happen"), + }; +} + +#[derive(Clone, Copy)] +struct Issue9939b<T, U> { + earthquake: T, + hurricane: U, +} + +fn issue9939b() { + let issue = Some(Issue9939b { + earthquake: true, + hurricane: 1, + }); + let (issue, drought, flood) = match issue { + //~^ manual_let_else + flood @ Some(Issue9939b { earthquake, hurricane }) => (flood, hurricane, earthquake), + None => unreachable!("can't happen"), + }; + assert_eq!(drought, 1); + assert!(flood); + assert!(issue.is_some()); + + // without shadowing + let (_y, erosion) = match issue { + //~^ manual_let_else + _x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake), + None => unreachable!("can't happen"), + }; + assert!(erosion); + + // with shadowing + let (_x, erosion) = match issue { + //~^ manual_let_else + _x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake), + None => unreachable!("can't happen"), + }; + assert!(erosion); +} diff --git a/src/tools/clippy/tests/ui/manual_let_else_match.stderr b/src/tools/clippy/tests/ui/manual_let_else_match.stderr index 393562c629b..ed6117ebffb 100644 --- a/src/tools/clippy/tests/ui/manual_let_else_match.stderr +++ b/src/tools/clippy/tests/ui/manual_let_else_match.stderr @@ -101,5 +101,75 @@ LL | | _ => unreachable!("can't happen"), LL | | }; | |______^ help: consider writing: `let Some(msg) = Some("hi") else { unreachable!("can't happen") };` -error: aborting due to 10 previous errors +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:188:5 + | +LL | / let tornado = match issue { +LL | | +LL | | Some(Issue9939 { avalanche }) => avalanche, +LL | | _ => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let Some(Issue9939 { avalanche: tornado }) = issue else { unreachable!("can't happen") };` + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:194:5 + | +LL | / let acid_rain = match issue { +LL | | +LL | | Some(Issue9939 { avalanche: tornado }) => tornado, +LL | | _ => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let Some(Issue9939 { avalanche: acid_rain }) = issue else { unreachable!("can't happen") };` + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:203:5 + | +LL | / let _y = match issue { +LL | | +LL | | _x @ Some(Issue9939 { avalanche }) => avalanche, +LL | | None => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let _x @ Some(Issue9939 { avalanche: _y }) = issue else { unreachable!("can't happen") };` + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:210:5 + | +LL | / let _x = match issue { +LL | | +LL | | _x @ Some(Issue9939 { avalanche }) => avalanche, +LL | | None => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let Some(Issue9939 { avalanche: _x }) = issue else { unreachable!("can't happen") };` + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:228:5 + | +LL | / let (issue, drought, flood) = match issue { +LL | | +LL | | flood @ Some(Issue9939b { earthquake, hurricane }) => (flood, hurricane, earthquake), +LL | | None => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let issue @ Some(Issue9939b { earthquake: flood, hurricane: drought }) = issue else { unreachable!("can't happen") };` + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:238:5 + | +LL | / let (_y, erosion) = match issue { +LL | | +LL | | _x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake), +LL | | None => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let _x @ Some(Issue9939b { earthquake: erosion, hurricane: _y }) = issue else { unreachable!("can't happen") };` + +error: this could be rewritten as `let...else` + --> tests/ui/manual_let_else_match.rs:246:5 + | +LL | / let (_x, erosion) = match issue { +LL | | +LL | | _x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake), +LL | | None => unreachable!("can't happen"), +LL | | }; + | |______^ help: consider writing: `let Some(Issue9939b { earthquake: erosion, hurricane: _x }) = issue else { unreachable!("can't happen") };` + +error: aborting due to 17 previous errors diff --git a/src/tools/clippy/tests/ui/missing_panics_doc.rs b/src/tools/clippy/tests/ui/missing_panics_doc.rs index ffdae8504f7..d016e099e30 100644 --- a/src/tools/clippy/tests/ui/missing_panics_doc.rs +++ b/src/tools/clippy/tests/ui/missing_panics_doc.rs @@ -250,3 +250,31 @@ pub fn issue_12760<const N: usize>() { } } } + +/// This needs documenting +pub fn unwrap_expect_etc_in_const() { + let a = const { std::num::NonZeroUsize::new(1).unwrap() }; + // This should still pass the lint even if it is guaranteed to panic at compile-time + let b = const { std::num::NonZeroUsize::new(0).unwrap() }; +} + +/// This needs documenting +pub const fn unwrap_expect_etc_in_const_fn_fails() { + //~^ missing_panics_doc + let a = std::num::NonZeroUsize::new(1).unwrap(); +} + +/// This needs documenting +pub const fn assert_in_const_fn_fails() { + //~^ missing_panics_doc + let x = 0; + if x == 0 { + panic!(); + } +} + +/// This needs documenting +pub const fn in_const_fn<const N: usize>(n: usize) { + //~^ missing_panics_doc + assert!(N > n); +} diff --git a/src/tools/clippy/tests/ui/missing_panics_doc.stderr b/src/tools/clippy/tests/ui/missing_panics_doc.stderr index 7f0acf8de9b..85a00914427 100644 --- a/src/tools/clippy/tests/ui/missing_panics_doc.stderr +++ b/src/tools/clippy/tests/ui/missing_panics_doc.stderr @@ -180,5 +180,41 @@ note: first possible panic found here LL | *v.last().expect("passed an empty thing") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 15 previous errors +error: docs for function which may panic missing `# Panics` section + --> tests/ui/missing_panics_doc.rs:262:1 + | +LL | pub const fn unwrap_expect_etc_in_const_fn_fails() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first possible panic found here + --> tests/ui/missing_panics_doc.rs:264:13 + | +LL | let a = std::num::NonZeroUsize::new(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: docs for function which may panic missing `# Panics` section + --> tests/ui/missing_panics_doc.rs:268:1 + | +LL | pub const fn assert_in_const_fn_fails() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first possible panic found here + --> tests/ui/missing_panics_doc.rs:272:9 + | +LL | panic!(); + | ^^^^^^^^ + +error: docs for function which may panic missing `# Panics` section + --> tests/ui/missing_panics_doc.rs:277:1 + | +LL | pub const fn in_const_fn<const N: usize>(n: usize) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first possible panic found here + --> tests/ui/missing_panics_doc.rs:279:5 + | +LL | assert!(N > n); + | ^^^^^^^^^^^^^^ + +error: aborting due to 18 previous errors diff --git a/src/tools/clippy/tests/ui/needless_bool_assign.fixed b/src/tools/clippy/tests/ui/needless_bool_assign.fixed index e0c717ecda2..d6fab4c51b5 100644 --- a/src/tools/clippy/tests/ui/needless_bool_assign.fixed +++ b/src/tools/clippy/tests/ui/needless_bool_assign.fixed @@ -33,3 +33,12 @@ fn main() { b = true; } } + +fn issue15063(x: bool, y: bool) { + let mut z = false; + + if x && y { + todo!() + } else { z = x || y; } + //~^^^^^ needless_bool_assign +} diff --git a/src/tools/clippy/tests/ui/needless_bool_assign.rs b/src/tools/clippy/tests/ui/needless_bool_assign.rs index 3e4fecefa78..c504f61f4dd 100644 --- a/src/tools/clippy/tests/ui/needless_bool_assign.rs +++ b/src/tools/clippy/tests/ui/needless_bool_assign.rs @@ -45,3 +45,16 @@ fn main() { b = true; } } + +fn issue15063(x: bool, y: bool) { + let mut z = false; + + if x && y { + todo!() + } else if x || y { + z = true; + } else { + z = false; + } + //~^^^^^ needless_bool_assign +} diff --git a/src/tools/clippy/tests/ui/needless_bool_assign.stderr b/src/tools/clippy/tests/ui/needless_bool_assign.stderr index f33a4bc0c59..1d09b8b25a0 100644 --- a/src/tools/clippy/tests/ui/needless_bool_assign.stderr +++ b/src/tools/clippy/tests/ui/needless_bool_assign.stderr @@ -51,5 +51,16 @@ LL | | } = note: `-D clippy::if-same-then-else` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::if_same_then_else)]` -error: aborting due to 4 previous errors +error: this if-then-else expression assigns a bool literal + --> tests/ui/needless_bool_assign.rs:54:12 + | +LL | } else if x || y { + | ____________^ +LL | | z = true; +LL | | } else { +LL | | z = false; +LL | | } + | |_____^ help: you can reduce it to: `{ z = x || y; }` + +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/neg_multiply.fixed b/src/tools/clippy/tests/ui/neg_multiply.fixed index ff6e08300e2..32d466e88fc 100644 --- a/src/tools/clippy/tests/ui/neg_multiply.fixed +++ b/src/tools/clippy/tests/ui/neg_multiply.fixed @@ -82,3 +82,15 @@ fn float() { -1.0 * -1.0; // should be ok } + +struct Y { + delta: f64, +} + +fn nested() { + let a = Y { delta: 1.0 }; + let b = Y { delta: 1.0 }; + let _ = (-(a.delta - 0.5).abs()).total_cmp(&1.0); + //~^ neg_multiply + let _ = (-(a.delta - 0.5).abs()).total_cmp(&1.0); +} diff --git a/src/tools/clippy/tests/ui/neg_multiply.rs b/src/tools/clippy/tests/ui/neg_multiply.rs index b0f4e85c78e..241a72c6d99 100644 --- a/src/tools/clippy/tests/ui/neg_multiply.rs +++ b/src/tools/clippy/tests/ui/neg_multiply.rs @@ -82,3 +82,15 @@ fn float() { -1.0 * -1.0; // should be ok } + +struct Y { + delta: f64, +} + +fn nested() { + let a = Y { delta: 1.0 }; + let b = Y { delta: 1.0 }; + let _ = ((a.delta - 0.5).abs() * -1.0).total_cmp(&1.0); + //~^ neg_multiply + let _ = (-(a.delta - 0.5).abs()).total_cmp(&1.0); +} diff --git a/src/tools/clippy/tests/ui/neg_multiply.stderr b/src/tools/clippy/tests/ui/neg_multiply.stderr index 2ef7e32ce05..f4fb6d3ce54 100644 --- a/src/tools/clippy/tests/ui/neg_multiply.stderr +++ b/src/tools/clippy/tests/ui/neg_multiply.stderr @@ -97,5 +97,11 @@ error: this multiplication by -1 can be written more succinctly LL | (3.0_f32 as f64) * -1.0; | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `-(3.0_f32 as f64)` -error: aborting due to 16 previous errors +error: this multiplication by -1 can be written more succinctly + --> tests/ui/neg_multiply.rs:93:13 + | +LL | let _ = ((a.delta - 0.5).abs() * -1.0).total_cmp(&1.0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-(a.delta - 0.5).abs())` + +error: aborting due to 17 previous errors diff --git a/src/tools/clippy/tests/ui/op_ref.fixed b/src/tools/clippy/tests/ui/op_ref.fixed index f412190b9fd..4bf4b91888c 100644 --- a/src/tools/clippy/tests/ui/op_ref.fixed +++ b/src/tools/clippy/tests/ui/op_ref.fixed @@ -110,3 +110,37 @@ mod issue_2597 { &array[idx] < val } } + +#[allow(clippy::needless_if)] +fn issue15063() { + use std::ops::BitAnd; + + macro_rules! mac { + ($e:expr) => { + $e.clone() + }; + } + + let x = 1; + if x == mac!(1) {} + //~^ op_ref + + #[derive(Copy, Clone)] + struct Y(i32); + impl BitAnd for Y { + type Output = Y; + fn bitand(self, rhs: Y) -> Y { + Y(self.0 & rhs.0) + } + } + impl<'a> BitAnd<&'a Y> for Y { + type Output = Y; + fn bitand(self, rhs: &'a Y) -> Y { + Y(self.0 & rhs.0) + } + } + let x = Y(1); + let y = Y(2); + let z = x & mac!(y); + //~^ op_ref +} diff --git a/src/tools/clippy/tests/ui/op_ref.rs b/src/tools/clippy/tests/ui/op_ref.rs index a4bbd86c7e9..9a192661aaf 100644 --- a/src/tools/clippy/tests/ui/op_ref.rs +++ b/src/tools/clippy/tests/ui/op_ref.rs @@ -110,3 +110,37 @@ mod issue_2597 { &array[idx] < val } } + +#[allow(clippy::needless_if)] +fn issue15063() { + use std::ops::BitAnd; + + macro_rules! mac { + ($e:expr) => { + $e.clone() + }; + } + + let x = 1; + if &x == &mac!(1) {} + //~^ op_ref + + #[derive(Copy, Clone)] + struct Y(i32); + impl BitAnd for Y { + type Output = Y; + fn bitand(self, rhs: Y) -> Y { + Y(self.0 & rhs.0) + } + } + impl<'a> BitAnd<&'a Y> for Y { + type Output = Y; + fn bitand(self, rhs: &'a Y) -> Y { + Y(self.0 & rhs.0) + } + } + let x = Y(1); + let y = Y(2); + let z = x & &mac!(y); + //~^ op_ref +} diff --git a/src/tools/clippy/tests/ui/op_ref.stderr b/src/tools/clippy/tests/ui/op_ref.stderr index 51c2963a9ee..8a58b154c81 100644 --- a/src/tools/clippy/tests/ui/op_ref.stderr +++ b/src/tools/clippy/tests/ui/op_ref.stderr @@ -36,5 +36,25 @@ LL | let _ = two + &three; | | | help: use the right value directly: `three` -error: aborting due to 4 previous errors +error: needlessly taken reference of both operands + --> tests/ui/op_ref.rs:125:8 + | +LL | if &x == &mac!(1) {} + | ^^^^^^^^^^^^^^ + | +help: use the values directly + | +LL - if &x == &mac!(1) {} +LL + if x == mac!(1) {} + | + +error: taken reference of right operand + --> tests/ui/op_ref.rs:144:13 + | +LL | let z = x & &mac!(y); + | ^^^^-------- + | | + | help: use the right value directly: `mac!(y)` + +error: aborting due to 6 previous errors diff --git a/src/tools/clippy/tests/ui/or_fun_call.fixed b/src/tools/clippy/tests/ui/or_fun_call.fixed index 34f3e046841..bcd2602edb6 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.fixed +++ b/src/tools/clippy/tests/ui/or_fun_call.fixed @@ -439,4 +439,24 @@ fn test_option_get_or_insert() { //~^ or_fun_call } +fn test_option_and() { + // assume that this is slow call + fn g() -> Option<u8> { + Some(99) + } + let mut x = Some(42_u8); + let _ = x.and_then(|_| g()); + //~^ or_fun_call +} + +fn test_result_and() { + // assume that this is slow call + fn g() -> Result<u8, ()> { + Ok(99) + } + let mut x: Result<u8, ()> = Ok(42); + let _ = x.and_then(|_| g()); + //~^ or_fun_call +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.rs b/src/tools/clippy/tests/ui/or_fun_call.rs index dc57bd6060a..8d1202ebf91 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.rs +++ b/src/tools/clippy/tests/ui/or_fun_call.rs @@ -439,4 +439,24 @@ fn test_option_get_or_insert() { //~^ or_fun_call } +fn test_option_and() { + // assume that this is slow call + fn g() -> Option<u8> { + Some(99) + } + let mut x = Some(42_u8); + let _ = x.and(g()); + //~^ or_fun_call +} + +fn test_result_and() { + // assume that this is slow call + fn g() -> Result<u8, ()> { + Ok(99) + } + let mut x: Result<u8, ()> = Ok(42); + let _ = x.and(g()); + //~^ or_fun_call +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.stderr b/src/tools/clippy/tests/ui/or_fun_call.stderr index 0f159fe8bff..585ee2d0e19 100644 --- a/src/tools/clippy/tests/ui/or_fun_call.stderr +++ b/src/tools/clippy/tests/ui/or_fun_call.stderr @@ -264,5 +264,17 @@ error: function call inside of `get_or_insert` LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` -error: aborting due to 41 previous errors +error: function call inside of `and` + --> tests/ui/or_fun_call.rs:448:15 + | +LL | let _ = x.and(g()); + | ^^^^^^^^ help: try: `and_then(|_| g())` + +error: function call inside of `and` + --> tests/ui/or_fun_call.rs:458:15 + | +LL | let _ = x.and(g()); + | ^^^^^^^^ help: try: `and_then(|_| g())` + +error: aborting due to 43 previous errors diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed index 099c118e64e..9f6643e8d52 100644 --- a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed @@ -60,7 +60,7 @@ fn issue9956() { //~^ redundant_closure_call // immediately calling only one closure, so we can't remove the other ones - let a = (|| || 123); + let a = || || 123; //~^ redundant_closure_call dbg!(a()()); @@ -144,3 +144,15 @@ fn issue_12358() { // different. make_closure!(x)(); } + +#[rustfmt::skip] +fn issue_9583() { + Some(true) == Some(true); + //~^ redundant_closure_call + Some(true) == Some(true); + //~^ redundant_closure_call + Some(if 1 > 2 {1} else {2}) == Some(2); + //~^ redundant_closure_call + Some( 1 > 2 ) == Some(true); + //~^ redundant_closure_call +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs index da5dd7ef263..34f22878678 100644 --- a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs @@ -144,3 +144,15 @@ fn issue_12358() { // different. make_closure!(x)(); } + +#[rustfmt::skip] +fn issue_9583() { + (|| { Some(true) })() == Some(true); + //~^ redundant_closure_call + (|| Some(true))() == Some(true); + //~^ redundant_closure_call + (|| { Some(if 1 > 2 {1} else {2}) })() == Some(2); + //~^ redundant_closure_call + (|| { Some( 1 > 2 ) })() == Some(true); + //~^ redundant_closure_call +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr index 2c35aafbe31..a5591cf7813 100644 --- a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr @@ -95,7 +95,7 @@ error: try not to call a closure in the expression where it is declared --> tests/ui/redundant_closure_call_fixable.rs:63:13 | LL | let a = (|| || || 123)(); - | ^^^^^^^^^^^^^^^^ help: try doing something like: `(|| || 123)` + | ^^^^^^^^^^^^^^^^ help: try doing something like: `|| || 123` error: try not to call a closure in the expression where it is declared --> tests/ui/redundant_closure_call_fixable.rs:68:13 @@ -145,5 +145,29 @@ error: try not to call a closure in the expression where it is declared LL | std::convert::identity((|| 13_i32 + 36_i32)()).leading_zeros(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try doing something like: `13_i32 + 36_i32` -error: aborting due to 17 previous errors +error: try not to call a closure in the expression where it is declared + --> tests/ui/redundant_closure_call_fixable.rs:150:5 + | +LL | (|| { Some(true) })() == Some(true); + | ^^^^^^^^^^^^^^^^^^^^^ help: try doing something like: `Some(true)` + +error: try not to call a closure in the expression where it is declared + --> tests/ui/redundant_closure_call_fixable.rs:152:5 + | +LL | (|| Some(true))() == Some(true); + | ^^^^^^^^^^^^^^^^^ help: try doing something like: `Some(true)` + +error: try not to call a closure in the expression where it is declared + --> tests/ui/redundant_closure_call_fixable.rs:154:5 + | +LL | (|| { Some(if 1 > 2 {1} else {2}) })() == Some(2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try doing something like: `Some(if 1 > 2 {1} else {2})` + +error: try not to call a closure in the expression where it is declared + --> tests/ui/redundant_closure_call_fixable.rs:156:5 + | +LL | (|| { Some( 1 > 2 ) })() == Some(true); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try doing something like: `Some( 1 > 2 )` + +error: aborting due to 21 previous errors diff --git a/src/tools/clippy/tests/ui/return_and_then.fixed b/src/tools/clippy/tests/ui/return_and_then.fixed index 8d9481d1595..8ee259b97f3 100644 --- a/src/tools/clippy/tests/ui/return_and_then.fixed +++ b/src/tools/clippy/tests/ui/return_and_then.fixed @@ -99,6 +99,92 @@ fn main() { }; None } + + #[expect(clippy::diverging_sub_expression)] + fn with_return_in_expression() -> Option<i32> { + _ = ( + return { + let x = Some("")?; + if x.len() > 2 { Some(3) } else { None } + }, + //~^ return_and_then + 10, + ); + } + + fn inside_if(a: bool, i: Option<u32>) -> Option<u32> { + if a { + let i = i?; + if i > 3 { Some(i) } else { None } + //~^ return_and_then + } else { + Some(42) + } + } + + fn inside_match(a: u32, i: Option<u32>) -> Option<u32> { + match a { + 1 | 2 => { + let i = i?; + if i > 3 { Some(i) } else { None } + }, + //~^ return_and_then + 3 | 4 => Some(42), + _ => None, + } + } + + fn inside_match_and_block_and_if(a: u32, i: Option<u32>) -> Option<u32> { + match a { + 1 | 2 => { + let a = a * 3; + if a.is_multiple_of(2) { + let i = i?; + if i > 3 { Some(i) } else { None } + //~^ return_and_then + } else { + Some(10) + } + }, + 3 | 4 => Some(42), + _ => None, + } + } + + #[expect(clippy::never_loop)] + fn with_break(i: Option<u32>) -> Option<u32> { + match i { + Some(1) => loop { + break ({ + let i = i?; + if i > 3 { Some(i) } else { None } + }); + //~^ return_and_then + }, + Some(2) => 'foo: loop { + loop { + break 'foo ({ + let i = i?; + if i > 3 { Some(i) } else { None } + }); + //~^ return_and_then + } + }, + Some(3) => 'bar: { + break 'bar ({ + let i = i?; + if i > 3 { Some(i) } else { None } + }); + //~^ return_and_then + }, + Some(4) => 'baz: loop { + _ = loop { + break i.and_then(|i| if i > 3 { Some(i) } else { None }); + }; + }, + _ => None, + } + } } fn gen_option(n: i32) -> Option<i32> { @@ -124,3 +210,48 @@ mod issue14781 { Ok(()) } } + +mod issue15111 { + #[derive(Debug)] + struct EvenOdd { + even: Option<u32>, + odd: Option<u32>, + } + + impl EvenOdd { + fn new(i: Option<u32>) -> Self { + Self { + even: i.and_then(|i| if i.is_multiple_of(2) { Some(i) } else { None }), + odd: i.and_then(|i| if i.is_multiple_of(2) { None } else { Some(i) }), + } + } + } + + fn with_if_let(i: Option<u32>) -> u32 { + if let Some(x) = i.and_then(|i| if i.is_multiple_of(2) { Some(i) } else { None }) { + x + } else { + std::hint::black_box(0) + } + } + + fn main() { + let _ = EvenOdd::new(Some(2)); + } +} + +mod issue14927 { + use std::path::Path; + struct A { + pub func: fn(check: bool, a: &Path, b: Option<&Path>), + } + const MY_A: A = A { + func: |check, a, b| { + if check { + let _ = (); + } else if let Some(parent) = b.and_then(|p| p.parent()) { + let _ = (); + } + }, + }; +} diff --git a/src/tools/clippy/tests/ui/return_and_then.rs b/src/tools/clippy/tests/ui/return_and_then.rs index beada921a91..dcb344f142b 100644 --- a/src/tools/clippy/tests/ui/return_and_then.rs +++ b/src/tools/clippy/tests/ui/return_and_then.rs @@ -90,6 +90,75 @@ fn main() { }; None } + + #[expect(clippy::diverging_sub_expression)] + fn with_return_in_expression() -> Option<i32> { + _ = ( + return Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }), + //~^ return_and_then + 10, + ); + } + + fn inside_if(a: bool, i: Option<u32>) -> Option<u32> { + if a { + i.and_then(|i| if i > 3 { Some(i) } else { None }) + //~^ return_and_then + } else { + Some(42) + } + } + + fn inside_match(a: u32, i: Option<u32>) -> Option<u32> { + match a { + 1 | 2 => i.and_then(|i| if i > 3 { Some(i) } else { None }), + //~^ return_and_then + 3 | 4 => Some(42), + _ => None, + } + } + + fn inside_match_and_block_and_if(a: u32, i: Option<u32>) -> Option<u32> { + match a { + 1 | 2 => { + let a = a * 3; + if a.is_multiple_of(2) { + i.and_then(|i| if i > 3 { Some(i) } else { None }) + //~^ return_and_then + } else { + Some(10) + } + }, + 3 | 4 => Some(42), + _ => None, + } + } + + #[expect(clippy::never_loop)] + fn with_break(i: Option<u32>) -> Option<u32> { + match i { + Some(1) => loop { + break i.and_then(|i| if i > 3 { Some(i) } else { None }); + //~^ return_and_then + }, + Some(2) => 'foo: loop { + loop { + break 'foo i.and_then(|i| if i > 3 { Some(i) } else { None }); + //~^ return_and_then + } + }, + Some(3) => 'bar: { + break 'bar i.and_then(|i| if i > 3 { Some(i) } else { None }); + //~^ return_and_then + }, + Some(4) => 'baz: loop { + _ = loop { + break i.and_then(|i| if i > 3 { Some(i) } else { None }); + }; + }, + _ => None, + } + } } fn gen_option(n: i32) -> Option<i32> { @@ -115,3 +184,48 @@ mod issue14781 { Ok(()) } } + +mod issue15111 { + #[derive(Debug)] + struct EvenOdd { + even: Option<u32>, + odd: Option<u32>, + } + + impl EvenOdd { + fn new(i: Option<u32>) -> Self { + Self { + even: i.and_then(|i| if i.is_multiple_of(2) { Some(i) } else { None }), + odd: i.and_then(|i| if i.is_multiple_of(2) { None } else { Some(i) }), + } + } + } + + fn with_if_let(i: Option<u32>) -> u32 { + if let Some(x) = i.and_then(|i| if i.is_multiple_of(2) { Some(i) } else { None }) { + x + } else { + std::hint::black_box(0) + } + } + + fn main() { + let _ = EvenOdd::new(Some(2)); + } +} + +mod issue14927 { + use std::path::Path; + struct A { + pub func: fn(check: bool, a: &Path, b: Option<&Path>), + } + const MY_A: A = A { + func: |check, a, b| { + if check { + let _ = (); + } else if let Some(parent) = b.and_then(|p| p.parent()) { + let _ = (); + } + }, + }; +} diff --git a/src/tools/clippy/tests/ui/return_and_then.stderr b/src/tools/clippy/tests/ui/return_and_then.stderr index 5feca882860..33867ea818a 100644 --- a/src/tools/clippy/tests/ui/return_and_then.stderr +++ b/src/tools/clippy/tests/ui/return_and_then.stderr @@ -146,5 +146,99 @@ LL + if x.len() > 2 { Some(3) } else { None } LL ~ }; | -error: aborting due to 10 previous errors +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:97:20 + | +LL | return Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ return { +LL + let x = Some("")?; +LL + if x.len() > 2 { Some(3) } else { None } +LL ~ }, + | + +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:105:13 + | +LL | i.and_then(|i| if i > 3 { Some(i) } else { None }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let i = i?; +LL + if i > 3 { Some(i) } else { None } + | + +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:114:22 + | +LL | 1 | 2 => i.and_then(|i| if i > 3 { Some(i) } else { None }), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ 1 | 2 => { +LL + let i = i?; +LL + if i > 3 { Some(i) } else { None } +LL ~ }, + | + +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:126:21 + | +LL | i.and_then(|i| if i > 3 { Some(i) } else { None }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let i = i?; +LL + if i > 3 { Some(i) } else { None } + | + +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:141:23 + | +LL | break i.and_then(|i| if i > 3 { Some(i) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ break ({ +LL + let i = i?; +LL + if i > 3 { Some(i) } else { None } +LL ~ }); + | + +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:146:32 + | +LL | break 'foo i.and_then(|i| if i > 3 { Some(i) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ break 'foo ({ +LL + let i = i?; +LL + if i > 3 { Some(i) } else { None } +LL ~ }); + | + +error: use the `?` operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:151:28 + | +LL | break 'bar i.and_then(|i| if i > 3 { Some(i) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ break 'bar ({ +LL + let i = i?; +LL + if i > 3 { Some(i) } else { None } +LL ~ }); + | + +error: aborting due to 17 previous errors 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 1820ade422f..603ab0accb0 100644 --- a/src/tools/clippy/tests/ui/std_instead_of_core.fixed +++ b/src/tools/clippy/tests/ui/std_instead_of_core.fixed @@ -8,7 +8,6 @@ extern crate alloc; #[macro_use] extern crate proc_macro_derive; -#[warn(clippy::std_instead_of_core)] fn std_instead_of_core() { // Regular import use core::hash::Hasher; @@ -90,9 +89,3 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: core::net::IpAddr) {} //~^ std_instead_of_core - -#[warn(clippy::std_instead_of_core)] -#[rustfmt::skip] -fn issue14982() { - use std::{collections::HashMap, hash::Hash}; -} 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 32c49330981..b6d4abad9f8 100644 --- a/src/tools/clippy/tests/ui/std_instead_of_core.rs +++ b/src/tools/clippy/tests/ui/std_instead_of_core.rs @@ -8,7 +8,6 @@ extern crate alloc; #[macro_use] extern crate proc_macro_derive; -#[warn(clippy::std_instead_of_core)] fn std_instead_of_core() { // Regular import use std::hash::Hasher; @@ -90,9 +89,3 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: std::net::IpAddr) {} //~^ std_instead_of_core - -#[warn(clippy::std_instead_of_core)] -#[rustfmt::skip] -fn issue14982() { - use std::{collections::HashMap, hash::Hash}; -} diff --git a/src/tools/clippy/tests/ui/std_instead_of_core.stderr b/src/tools/clippy/tests/ui/std_instead_of_core.stderr index 45d60d235ce..a5f8fbbe37c 100644 --- a/src/tools/clippy/tests/ui/std_instead_of_core.stderr +++ b/src/tools/clippy/tests/ui/std_instead_of_core.stderr @@ -1,5 +1,5 @@ error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:14:9 + --> tests/ui/std_instead_of_core.rs:13:9 | LL | use std::hash::Hasher; | ^^^ help: consider importing the item from `core`: `core` @@ -8,61 +8,61 @@ LL | use std::hash::Hasher; = help: to override `-D warnings` add `#[allow(clippy::std_instead_of_core)]` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:17:11 + --> tests/ui/std_instead_of_core.rs:16:11 | LL | use ::std::hash::Hash; | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:23:9 + --> tests/ui/std_instead_of_core.rs:22:9 | LL | use std::fmt::{Debug, Result}; | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:28:9 + --> tests/ui/std_instead_of_core.rs:27:9 | LL | use std::{ | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:35:15 + --> tests/ui/std_instead_of_core.rs:34:15 | LL | let ptr = std::ptr::null::<u32>(); | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:37:21 + --> tests/ui/std_instead_of_core.rs:36:21 | LL | let ptr_mut = ::std::ptr::null_mut::<usize>(); | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:41:16 + --> tests/ui/std_instead_of_core.rs:40:16 | LL | let cell = std::cell::Cell::new(8u32); | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:43:27 + --> tests/ui/std_instead_of_core.rs:42:27 | LL | let cell_absolute = ::std::cell::Cell::new(8u32); | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:48:9 + --> tests/ui/std_instead_of_core.rs:47:9 | LL | use std::error::Error; | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:52:9 + --> tests/ui/std_instead_of_core.rs:51:9 | LL | use std::iter::Iterator; | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `alloc` - --> tests/ui/std_instead_of_core.rs:59:9 + --> tests/ui/std_instead_of_core.rs:58:9 | LL | use std::vec; | ^^^ help: consider importing the item from `alloc`: `alloc` @@ -71,13 +71,13 @@ LL | use std::vec; = help: to override `-D warnings` add `#[allow(clippy::std_instead_of_alloc)]` error: used import from `std` instead of `alloc` - --> tests/ui/std_instead_of_core.rs:61:9 + --> tests/ui/std_instead_of_core.rs:60:9 | LL | use std::vec::Vec; | ^^^ help: consider importing the item from `alloc`: `alloc` error: used import from `alloc` instead of `core` - --> tests/ui/std_instead_of_core.rs:67:9 + --> tests/ui/std_instead_of_core.rs:66:9 | LL | use alloc::slice::from_ref; | ^^^^^ help: consider importing the item from `core`: `core` @@ -86,13 +86,13 @@ LL | use alloc::slice::from_ref; = help: to override `-D warnings` add `#[allow(clippy::alloc_instead_of_core)]` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:82:9 + --> tests/ui/std_instead_of_core.rs:81:9 | LL | std::intrinsics::copy(a, b, 1); | ^^^ help: consider importing the item from `core`: `core` error: used import from `std` instead of `core` - --> tests/ui/std_instead_of_core.rs:91:17 + --> tests/ui/std_instead_of_core.rs:90:17 | LL | fn msrv_1_77(_: std::net::IpAddr) {} | ^^^ help: consider importing the item from `core`: `core` diff --git a/src/tools/clippy/tests/ui/std_instead_of_core_unfixable.rs b/src/tools/clippy/tests/ui/std_instead_of_core_unfixable.rs new file mode 100644 index 00000000000..957f472a454 --- /dev/null +++ b/src/tools/clippy/tests/ui/std_instead_of_core_unfixable.rs @@ -0,0 +1,18 @@ +//@no-rustfix + +#![warn(clippy::std_instead_of_core)] +#![warn(clippy::std_instead_of_alloc)] +#![allow(unused_imports)] + +#[rustfmt::skip] +fn issue14982() { + use std::{collections::HashMap, hash::Hash}; + //~^ std_instead_of_core +} + +#[rustfmt::skip] +fn issue15143() { + use std::{error::Error, vec::Vec, fs::File}; + //~^ std_instead_of_core + //~| std_instead_of_alloc +} diff --git a/src/tools/clippy/tests/ui/std_instead_of_core_unfixable.stderr b/src/tools/clippy/tests/ui/std_instead_of_core_unfixable.stderr new file mode 100644 index 00000000000..0cdec56c992 --- /dev/null +++ b/src/tools/clippy/tests/ui/std_instead_of_core_unfixable.stderr @@ -0,0 +1,30 @@ +error: used import from `std` instead of `core` + --> tests/ui/std_instead_of_core_unfixable.rs:9:43 + | +LL | use std::{collections::HashMap, hash::Hash}; + | ^^^^ + | + = help: consider importing the item from `core` + = note: `-D clippy::std-instead-of-core` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::std_instead_of_core)]` + +error: used import from `std` instead of `core` + --> tests/ui/std_instead_of_core_unfixable.rs:15:22 + | +LL | use std::{error::Error, vec::Vec, fs::File}; + | ^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `alloc` + --> tests/ui/std_instead_of_core_unfixable.rs:15:34 + | +LL | use std::{error::Error, vec::Vec, fs::File}; + | ^^^ + | + = help: consider importing the item from `alloc` + = note: `-D clippy::std-instead-of-alloc` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::std_instead_of_alloc)]` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/swap_with_temporary.fixed b/src/tools/clippy/tests/ui/swap_with_temporary.fixed index 4007d998ba0..4b4b0d4aebd 100644 --- a/src/tools/clippy/tests/ui/swap_with_temporary.fixed +++ b/src/tools/clippy/tests/ui/swap_with_temporary.fixed @@ -72,3 +72,49 @@ fn dont_lint_those(s: &mut S, v: &mut [String], w: Option<&mut String>) { swap(&mut s.t, v.get_mut(0).unwrap()); swap(w.unwrap(), &mut s.t); } + +fn issue15166() { + use std::sync::Mutex; + + struct A { + thing: Mutex<Vec<u8>>, + } + + impl A { + fn a(&self) { + let mut new_vec = vec![42]; + // Do not lint here, as neither `new_vec` nor the result of `.lock().unwrap()` are temporaries + swap(&mut new_vec, &mut self.thing.lock().unwrap()); + for v in new_vec { + // Do something with v + } + // Here `vec![42]` is temporary though, and a proper dereference will have to be used in the fix + *self.thing.lock().unwrap() = vec![42]; + //~^ ERROR: swapping with a temporary value is inefficient + } + } +} + +fn multiple_deref() { + let mut v1 = &mut &mut &mut vec![42]; + ***v1 = vec![]; + //~^ ERROR: swapping with a temporary value is inefficient + + struct Wrapper<T: ?Sized>(T); + impl<T: ?Sized> std::ops::Deref for Wrapper<T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl<T: ?Sized> std::ops::DerefMut for Wrapper<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + use std::sync::Mutex; + let mut v1 = Mutex::new(Wrapper(Wrapper(vec![42]))); + ***v1.lock().unwrap() = vec![]; + //~^ ERROR: swapping with a temporary value is inefficient +} diff --git a/src/tools/clippy/tests/ui/swap_with_temporary.rs b/src/tools/clippy/tests/ui/swap_with_temporary.rs index d403c086c0f..8e35e6144d9 100644 --- a/src/tools/clippy/tests/ui/swap_with_temporary.rs +++ b/src/tools/clippy/tests/ui/swap_with_temporary.rs @@ -72,3 +72,49 @@ fn dont_lint_those(s: &mut S, v: &mut [String], w: Option<&mut String>) { swap(&mut s.t, v.get_mut(0).unwrap()); swap(w.unwrap(), &mut s.t); } + +fn issue15166() { + use std::sync::Mutex; + + struct A { + thing: Mutex<Vec<u8>>, + } + + impl A { + fn a(&self) { + let mut new_vec = vec![42]; + // Do not lint here, as neither `new_vec` nor the result of `.lock().unwrap()` are temporaries + swap(&mut new_vec, &mut self.thing.lock().unwrap()); + for v in new_vec { + // Do something with v + } + // Here `vec![42]` is temporary though, and a proper dereference will have to be used in the fix + swap(&mut vec![42], &mut self.thing.lock().unwrap()); + //~^ ERROR: swapping with a temporary value is inefficient + } + } +} + +fn multiple_deref() { + let mut v1 = &mut &mut &mut vec![42]; + swap(&mut ***v1, &mut vec![]); + //~^ ERROR: swapping with a temporary value is inefficient + + struct Wrapper<T: ?Sized>(T); + impl<T: ?Sized> std::ops::Deref for Wrapper<T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl<T: ?Sized> std::ops::DerefMut for Wrapper<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + use std::sync::Mutex; + let mut v1 = Mutex::new(Wrapper(Wrapper(vec![42]))); + swap(&mut vec![], &mut v1.lock().unwrap()); + //~^ ERROR: swapping with a temporary value is inefficient +} diff --git a/src/tools/clippy/tests/ui/swap_with_temporary.stderr b/src/tools/clippy/tests/ui/swap_with_temporary.stderr index 59355771a96..5ca4fccd37a 100644 --- a/src/tools/clippy/tests/ui/swap_with_temporary.stderr +++ b/src/tools/clippy/tests/ui/swap_with_temporary.stderr @@ -96,5 +96,41 @@ note: this expression returns a temporary value LL | swap(mac!(refmut y), &mut func()); | ^^^^^^ -error: aborting due to 8 previous errors +error: swapping with a temporary value is inefficient + --> tests/ui/swap_with_temporary.rs:92:13 + | +LL | swap(&mut vec![42], &mut self.thing.lock().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use assignment instead: `*self.thing.lock().unwrap() = vec![42]` + | +note: this expression returns a temporary value + --> tests/ui/swap_with_temporary.rs:92:23 + | +LL | swap(&mut vec![42], &mut self.thing.lock().unwrap()); + | ^^^^^^^^ + +error: swapping with a temporary value is inefficient + --> tests/ui/swap_with_temporary.rs:100:5 + | +LL | swap(&mut ***v1, &mut vec![]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use assignment instead: `***v1 = vec![]` + | +note: this expression returns a temporary value + --> tests/ui/swap_with_temporary.rs:100:27 + | +LL | swap(&mut ***v1, &mut vec![]); + | ^^^^^^ + +error: swapping with a temporary value is inefficient + --> tests/ui/swap_with_temporary.rs:118:5 + | +LL | swap(&mut vec![], &mut v1.lock().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use assignment instead: `***v1.lock().unwrap() = vec![]` + | +note: this expression returns a temporary value + --> tests/ui/swap_with_temporary.rs:118:15 + | +LL | swap(&mut vec![], &mut v1.lock().unwrap()); + | ^^^^^^ + +error: aborting due to 11 previous errors diff --git a/src/tools/clippy/tests/ui/track-diagnostics-clippy.rs b/src/tools/clippy/tests/ui/track-diagnostics-clippy.rs index 2e67fb65efc..3bae23f1984 100644 --- a/src/tools/clippy/tests/ui/track-diagnostics-clippy.rs +++ b/src/tools/clippy/tests/ui/track-diagnostics-clippy.rs @@ -4,6 +4,7 @@ // Normalize the emitted location so this doesn't need // updating everytime someone adds or removes a line. //@normalize-stderr-test: ".rs:\d+:\d+" -> ".rs:LL:CC" +//@normalize-stderr-test: "src/tools/clippy/" -> "" #![warn(clippy::let_and_return, clippy::unnecessary_cast)] @@ -12,7 +13,7 @@ fn main() { let a = 3u32; let b = a as u32; //~^ unnecessary_cast - + // Check the provenance of a lint sent through `TyCtxt::node_span_lint()` let c = { let d = 42; diff --git a/src/tools/clippy/tests/ui/track-diagnostics-clippy.stderr b/src/tools/clippy/tests/ui/track-diagnostics-clippy.stderr index 9d6538112bf..d5533877b45 100644 --- a/src/tools/clippy/tests/ui/track-diagnostics-clippy.stderr +++ b/src/tools/clippy/tests/ui/track-diagnostics-clippy.stderr @@ -4,7 +4,7 @@ error: casting to the same type is unnecessary (`u32` -> `u32`) LL | let b = a as u32; | ^^^^^^^^ help: try: `a` | - = note: -Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs:LL:CC + = note: -Ztrack-diagnostics: created at clippy_lints/src/casts/unnecessary_cast.rs:LL:CC = note: `-D clippy::unnecessary-cast` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_cast)]` @@ -16,7 +16,7 @@ LL | let d = 42; LL | d | ^ | - = note: -Ztrack-diagnostics: created at src/tools/clippy/clippy_lints/src/returns.rs:LL:CC + = note: -Ztrack-diagnostics: created at clippy_lints/src/returns.rs:LL:CC = note: `-D clippy::let-and-return` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::let_and_return)]` help: return the expression directly diff --git a/src/tools/clippy/tests/ui/unnecessary_map_or.fixed b/src/tools/clippy/tests/ui/unnecessary_map_or.fixed index 3c724397284..3109c4af8e2 100644 --- a/src/tools/clippy/tests/ui/unnecessary_map_or.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_map_or.fixed @@ -130,3 +130,13 @@ fn issue14201(a: Option<String>, b: Option<String>, s: &String) -> bool { //~^ unnecessary_map_or x && y } + +fn issue15180() { + let s = std::sync::Mutex::new(Some("foo")); + _ = s.lock().unwrap().is_some_and(|s| s == "foo"); + //~^ unnecessary_map_or + + let s = &&&&Some("foo"); + _ = s.is_some_and(|s| s == "foo"); + //~^ unnecessary_map_or +} diff --git a/src/tools/clippy/tests/ui/unnecessary_map_or.rs b/src/tools/clippy/tests/ui/unnecessary_map_or.rs index e734a27bada..52a55f9fc9e 100644 --- a/src/tools/clippy/tests/ui/unnecessary_map_or.rs +++ b/src/tools/clippy/tests/ui/unnecessary_map_or.rs @@ -134,3 +134,13 @@ fn issue14201(a: Option<String>, b: Option<String>, s: &String) -> bool { //~^ unnecessary_map_or x && y } + +fn issue15180() { + let s = std::sync::Mutex::new(Some("foo")); + _ = s.lock().unwrap().map_or(false, |s| s == "foo"); + //~^ unnecessary_map_or + + let s = &&&&Some("foo"); + _ = s.map_or(false, |s| s == "foo"); + //~^ unnecessary_map_or +} diff --git a/src/tools/clippy/tests/ui/unnecessary_map_or.stderr b/src/tools/clippy/tests/ui/unnecessary_map_or.stderr index 0f9466a6a6b..99e17e8b34b 100644 --- a/src/tools/clippy/tests/ui/unnecessary_map_or.stderr +++ b/src/tools/clippy/tests/ui/unnecessary_map_or.stderr @@ -326,5 +326,29 @@ LL - let y = b.map_or(true, |b| b == *s); LL + let y = b.is_none_or(|b| b == *s); | -error: aborting due to 26 previous errors +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:140:9 + | +LL | _ = s.lock().unwrap().map_or(false, |s| s == "foo"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - _ = s.lock().unwrap().map_or(false, |s| s == "foo"); +LL + _ = s.lock().unwrap().is_some_and(|s| s == "foo"); + | + +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:144:9 + | +LL | _ = s.map_or(false, |s| s == "foo"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - _ = s.map_or(false, |s| s == "foo"); +LL + _ = s.is_some_and(|s| s == "foo"); + | + +error: aborting due to 28 previous errors diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.fixed b/src/tools/clippy/tests/ui/unnecessary_operation.fixed index 645b56fe95e..ac9fa4de20a 100644 --- a/src/tools/clippy/tests/ui/unnecessary_operation.fixed +++ b/src/tools/clippy/tests/ui/unnecessary_operation.fixed @@ -144,3 +144,16 @@ const fn foo() { assert!([42, 55].len() > get_usize()); //~^ unnecessary_operation } + +fn issue15173() { + // No lint as `Box::new(None)` alone would be ambiguous + Box::new(None) as Box<Option<i32>>; +} + +#[expect(clippy::redundant_closure_call)] +fn issue15173_original<MsU>(handler: impl FnOnce() -> MsU + Clone + 'static) { + Box::new(move |value| { + (|_| handler.clone()())(value); + None + }) as Box<dyn Fn(i32) -> Option<i32>>; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.rs b/src/tools/clippy/tests/ui/unnecessary_operation.rs index 97e90269c5c..a3e6c6288ad 100644 --- a/src/tools/clippy/tests/ui/unnecessary_operation.rs +++ b/src/tools/clippy/tests/ui/unnecessary_operation.rs @@ -150,3 +150,16 @@ const fn foo() { [42, 55][get_usize()]; //~^ unnecessary_operation } + +fn issue15173() { + // No lint as `Box::new(None)` alone would be ambiguous + Box::new(None) as Box<Option<i32>>; +} + +#[expect(clippy::redundant_closure_call)] +fn issue15173_original<MsU>(handler: impl FnOnce() -> MsU + Clone + 'static) { + Box::new(move |value| { + (|_| handler.clone()())(value); + None + }) as Box<dyn Fn(i32) -> Option<i32>>; +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.fixed b/src/tools/clippy/tests/ui/zero_ptr.fixed index f2375d57f3a..f9d9d2db176 100644 --- a/src/tools/clippy/tests/ui/zero_ptr.fixed +++ b/src/tools/clippy/tests/ui/zero_ptr.fixed @@ -16,3 +16,11 @@ fn main() { let z = 0; let _ = z as *const usize; // this is currently not caught } + +const fn in_const_context() { + #[clippy::msrv = "1.23"] + let _: *const usize = 0 as *const _; + #[clippy::msrv = "1.24"] + let _: *const usize = std::ptr::null(); + //~^ zero_ptr +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.rs b/src/tools/clippy/tests/ui/zero_ptr.rs index ee01e426a43..41455fee5b5 100644 --- a/src/tools/clippy/tests/ui/zero_ptr.rs +++ b/src/tools/clippy/tests/ui/zero_ptr.rs @@ -16,3 +16,11 @@ fn main() { let z = 0; let _ = z as *const usize; // this is currently not caught } + +const fn in_const_context() { + #[clippy::msrv = "1.23"] + let _: *const usize = 0 as *const _; + #[clippy::msrv = "1.24"] + let _: *const usize = 0 as *const _; + //~^ zero_ptr +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.stderr b/src/tools/clippy/tests/ui/zero_ptr.stderr index 8dc781f3625..81269de6c60 100644 --- a/src/tools/clippy/tests/ui/zero_ptr.stderr +++ b/src/tools/clippy/tests/ui/zero_ptr.stderr @@ -31,5 +31,11 @@ error: `0 as *mut _` detected LL | foo(0 as *const _, 0 as *mut _); | ^^^^^^^^^^^ help: try: `std::ptr::null_mut()` -error: aborting due to 5 previous errors +error: `0 as *const _` detected + --> tests/ui/zero_ptr.rs:24:27 + | +LL | let _: *const usize = 0 as *const _; + | ^^^^^^^^^^^^^ help: try: `std::ptr::null()` + +error: aborting due to 6 previous errors diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml index 4f370758c00..805baf2af6d 100644 --- a/src/tools/clippy/triagebot.toml +++ b/src/tools/clippy/triagebot.toml @@ -15,6 +15,8 @@ allow-unauthenticated = [ [close] +[transfer] + [issue-links] [mentions."clippy_lints/src/doc"] @@ -43,12 +45,15 @@ reviewed_label = "S-waiting-on-author" [autolabel."S-waiting-on-review"] new_pr = true +[concern] +# These labels are set when there are unresolved concerns, removed otherwise +labels = ["S-waiting-on-concerns"] + [assign] contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md" users_on_vacation = [ "matthiaskrgr", "Manishearth", - "blyxyas", ] [assign.owners] diff --git a/src/tools/clippy/util/gh-pages/index_template.html b/src/tools/clippy/util/gh-pages/index_template.html index 865b9523c39..6f380ec8fee 100644 --- a/src/tools/clippy/util/gh-pages/index_template.html +++ b/src/tools/clippy/util/gh-pages/index_template.html @@ -149,49 +149,45 @@ Otherwise, have a great day =^.^= <article class="panel panel-default" id="{{lint.id}}"> {# #} <input id="label-{{lint.id}}" type="checkbox"> {# #} <label for="label-{{lint.id}}"> {# #} - <header class="panel-heading"> {# #} - <h2 class="panel-title"> {# #} - <div class="panel-title-name" id="lint-{{lint.id}}"> {# #} - <span>{{lint.id}}</span> {#+ #} - <a href="#{{lint.id}}" class="lint-anchor anchor label label-default">¶</a> {#+ #} - <a href="" class="copy-to-clipboard anchor label label-default"> {# #} - 📋 {# #} - </a> {# #} - </div> {# #} + <h2 class="lint-title"> {# #} + <div class="panel-title-name" id="lint-{{lint.id}}"> {# #} + {{lint.id +}} + <a href="#{{lint.id}}" class="anchor label label-default">¶</a> {#+ #} + <a href="" class="copy-to-clipboard anchor label label-default"> {# #} + 📋 {# #} + </a> {# #} + </div> {# #} - <div class="panel-title-addons"> {# #} - <span class="label label-lint-group label-default label-group-{{lint.group}}">{{lint.group}}</span> {#+ #} + <span class="label label-lint-group label-default label-group-{{lint.group}}">{{lint.group}}</span> {#+ #} - <span class="label label-lint-level label-lint-level-{{lint.level}}">{{lint.level}}</span> {#+ #} + <span class="label label-lint-level label-lint-level-{{lint.level}}">{{lint.level}}</span> {#+ #} - <span class="label label-doc-folding"></span> {# #} - </div> {# #} - </h2> {# #} - </header> {# #} + <span class="label label-doc-folding"></span> {# #} + </h2> {# #} </label> {# #} <div class="list-group lint-docs"> {# #} <div class="list-group-item lint-doc-md">{{Self::markdown(lint.docs)}}</div> {# #} <div class="lint-additional-info-container"> {# Applicability #} - <div class="lint-additional-info-item"> {# #} - <span> Applicability: </span> {# #} + <div> {# #} + Applicability: {#+ #} <span class="label label-default label-applicability">{{ lint.applicability_str() }}</span> {# #} <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.Applicability.html#variants">(?)</a> {# #} </div> {# Clippy version #} - <div class="lint-additional-info-item"> {# #} - <span>{% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: </span> {# #} + <div> {# #} + {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {#+ #} <span class="label label-default label-version">{{lint.version}}</span> {# #} </div> {# Open related issues #} - <div class="lint-additional-info-item"> {# #} + <div> {# #} <a href="https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+{{lint.id}}">Related Issues</a> {# #} </div> {# Jump to source #} {% if let Some(id_location) = lint.id_location %} - <div class="lint-additional-info-item"> {# #} + <div> {# #} <a href="https://github.com/rust-lang/rust-clippy/blob/master/{{id_location}}">View Source</a> {# #} </div> {% endif %} diff --git a/src/tools/clippy/util/gh-pages/script.js b/src/tools/clippy/util/gh-pages/script.js index 285aa34e701..ee13f1c0cd8 100644 --- a/src/tools/clippy/util/gh-pages/script.js +++ b/src/tools/clippy/util/gh-pages/script.js @@ -554,10 +554,10 @@ function addListeners() { return; } - if (event.target.classList.contains("lint-anchor")) { - lintAnchor(event); - } else if (event.target.classList.contains("copy-to-clipboard")) { + if (event.target.classList.contains("copy-to-clipboard")) { copyToClipboard(event); + } else if (event.target.classList.contains("anchor")) { + lintAnchor(event); } }); diff --git a/src/tools/clippy/util/gh-pages/style.css b/src/tools/clippy/util/gh-pages/style.css index 3cc7a919c23..022ea875200 100644 --- a/src/tools/clippy/util/gh-pages/style.css +++ b/src/tools/clippy/util/gh-pages/style.css @@ -50,11 +50,25 @@ div.panel div.panel-body button.open { .panel-heading { cursor: pointer; } -.panel-title { display: flex; flex-wrap: wrap;} -.panel-title .label { display: inline-block; } +.lint-title { + cursor: pointer; + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + display: flex; + flex-wrap: wrap; + background: var(--theme-hover); + color: var(--fg); + border: 1px solid var(--theme-popup-border); + padding: 10px 15px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + gap: 4px; +} + +.lint-title .label { display: inline-block; } .panel-title-name { flex: 1; min-width: 400px;} -.panel-title-name span { vertical-align: bottom; } .panel .panel-title-name .anchor { display: none; } .panel:hover .panel-title-name .anchor { display: inline;} @@ -147,7 +161,7 @@ div.panel div.panel-body button.open { display: flex; flex-flow: column; } - .lint-additional-info-item + .lint-additional-info-item { + .lint-additional-info-container > div + div { border-top: 1px solid var(--theme-popup-border); } } @@ -156,12 +170,12 @@ div.panel div.panel-body button.open { display: flex; flex-flow: row; } - .lint-additional-info-item + .lint-additional-info-item { + .lint-additional-info-container > div + div { border-left: 1px solid var(--theme-popup-border); } } -.lint-additional-info-item { +.lint-additional-info-container > div { display: inline-flex; min-width: 200px; flex-grow: 1; diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 3e879e0e4bb..933a32392bd 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1777,6 +1777,12 @@ impl<'test> TestCx<'test> { // Allow tests to use internal features. rustc.args(&["-A", "internal_features"]); + // Allow tests to have unused parens and braces. + // Add #![deny(unused_parens, unused_braces)] to the test file if you want to + // test that these lints are working. + rustc.args(&["-A", "unused_parens"]); + rustc.args(&["-A", "unused_braces"]); + if self.props.force_host { self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags); if !is_rustdoc { diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a9394819541..0af6b8c6fc7 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -688ea65df6a47866d0f72a00f1e18b47a7edf83b +32cd9114712a24010b0583624dc52ac302194128 diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 693b8916d89..9c077e5b7b6 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1014,8 +1014,6 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { const PANIC_ON_ALLOC_FAIL: bool = false; - const TRACING_ENABLED: bool = cfg!(feature = "tracing"); - #[inline(always)] fn enforce_alignment(ecx: &MiriInterpCx<'tcx>) -> bool { ecx.machine.check_alignment != AlignmentCheck::None @@ -1827,6 +1825,19 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { #[cfg(not(target_os = "linux"))] MiriAllocParams::Global } + + fn enter_trace_span(span: impl FnOnce() -> tracing::Span) -> impl EnteredTraceSpan { + #[cfg(feature = "tracing")] + { + span().entered() + } + #[cfg(not(feature = "tracing"))] + #[expect(clippy::unused_unit)] + { + let _ = span; // so we avoid the "unused variable" warning + () + } + } } /// Trait for callbacks handling asynchronous machine operations. diff --git a/src/tools/opt-dist/src/tests.rs b/src/tools/opt-dist/src/tests.rs index 705a1750ae8..2d2aab86eda 100644 --- a/src/tools/opt-dist/src/tests.rs +++ b/src/tools/opt-dist/src/tests.rs @@ -106,7 +106,10 @@ llvm-config = "{llvm_config}" "tests/incremental", "tests/mir-opt", "tests/pretty", + // Make sure that we don't use too new GLIBC symbols on x64 "tests/run-make/glibc-symbols-x86_64-unknown-linux-gnu", + // Make sure that we use LLD by default on x64 + "tests/run-make/rust-lld-x86_64-unknown-linux-gnu-dist", "tests/ui", "tests/crashes", ]; diff --git a/src/tools/run-make-support/CHANGELOG.md b/src/tools/run-make-support/CHANGELOG.md deleted file mode 100644 index c1b7b618a92..00000000000 --- a/src/tools/run-make-support/CHANGELOG.md +++ /dev/null @@ -1,83 +0,0 @@ -# Changelog - -All notable changes to the `run_make_support` library should be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the support -library should adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) even if it's -not intended for public consumption (it's moreso to help internally, to help test writers track -changes to the support library). - -This support library will probably never reach 1.0. Please bump the minor version in `Cargo.toml` if -you make any breaking changes or other significant changes, or bump the patch version for bug fixes. - -## [0.2.0] - 2024-06-11 - -### Added - -- Added `fs_wrapper` module which provides panic-on-fail helpers for their respective `std::fs` - counterparts, the motivation is to: - - Reduce littering `.unwrap()` or `.expect()` everywhere for fs operations - - Help the test writer avoid forgetting to check fs results (even though enforced by - `-Dunused_must_use`) - - Provide better panic messages by default -- Added `path()` helper which creates a `Path` relative to `cwd()` (but is less noisy). - -### Changed - -- Marked many functions with `#[must_use]`, and rmake.rs are now compiled with `-Dunused_must_use`. - -## [0.1.0] - 2024-06-09 - -### Changed - -- Use *drop bombs* to enforce that commands are executed; a command invocation will panic if it is - constructed but never executed. Execution methods `Command::{run, run_fail}` will defuse the drop - bomb. -- Added `Command` helpers that forward to `std::process::Command` counterparts. - -### Removed - -- The `env_var` method which was incorrectly named and is `env_clear` underneath and is a footgun - from `impl_common_helpers`. For example, removing `TMPDIR` on Unix and `TMP`/`TEMP` breaks - `std::env::temp_dir` and wrecks anything using that, such as rustc's codgen. -- Removed `Deref`/`DerefMut` for `run_make_support::Command` -> `std::process::Command` because it - causes a method chain like `htmldocck().arg().run()` to fail, because `arg()` resolves to - `std::process::Command` which also returns a `&mut std::process::Command`, causing the `run()` to - be not found. - -## [0.0.0] - 2024-06-09 - -Consider this version to contain all changes made to the support library before we started to track -changes in this changelog. - -### Added - -- Custom command wrappers around `std::process::Command` (`run_make_support::Command`) and custom - wrapper around `std::process::Output` (`CompletedProcess`) to make it more convenient to work with - commands and their output, and help avoid forgetting to check for exit status. - - `Command`: `set_stdin`, `run`, `run_fail`. - - `CompletedProcess`: `std{err,out}_utf8`, `status`, `assert_std{err,out}_{equals, contains, - not_contains}`, `assert_exit_code`. -- `impl_common_helpers` macro to avoid repeating adding common convenience methods, including: - - Environment manipulation methods: `env`, `env_remove` - - Command argument providers: `arg`, `args` - - Common invocation inspection (of the command invocation up until `inspect` is called): - `inspect` - - Execution methods: `run` (for commands expected to succeed execution, exit status `0`) and - `run_fail` (for commands expected to fail execution, exit status non-zero). -- Command wrappers around: `rustc`, `clang`, `cc`, `rustc`, `rustdoc`, `llvm-readobj`. -- Thin helpers to construct `python` and `htmldocck` commands. -- `run` and `run_fail` (like `Command::{run, run_fail}`) for running binaries, which sets suitable - env vars (like `LD_LIB_PATH` or equivalent, `TARGET_RPATH_ENV`, `PATH` on Windows). -- Pseudo command `diff` which has similar functionality as the cli util but not the same API. -- Convenience panic-on-fail helpers `env_var`, `env_var_os`, `cwd` for their `std::env` conterparts. -- Convenience panic-on-fail helpers for reading respective env vars: `target`, `source_root`. -- Platform check helpers: `is_windows`, `is_msvc`, `cygpath_windows`, `uname`. -- fs helpers: `copy_dir_all`. -- `recursive_diff` helper. -- Generic `assert_not_contains` helper. -- Scoped run-with-teardown helper `run_in_tmpdir` which is designed to run commands in a temporary - directory that is cleared when closure returns. -- Helpers for constructing the name of binaries and libraries: `rust_lib_name`, `static_lib_name`, - `bin_name`, `dynamic_lib_name`. -- Re-export libraries: `gimli`, `object`, `regex`, `wasmparsmer`. diff --git a/src/tools/run-make-support/Cargo.toml b/src/tools/run-make-support/Cargo.toml index 3226f467ba4..a4e7534137d 100644 --- a/src/tools/run-make-support/Cargo.toml +++ b/src/tools/run-make-support/Cargo.toml @@ -1,18 +1,27 @@ [package] name = "run_make_support" -version = "0.2.0" -edition = "2021" +version = "0.0.0" +edition = "2024" [dependencies] + +# These dependencies are either used to implement part of support library +# functionality, or re-exported to test recipe programs via the support library, +# or both. + +# tidy-alphabetical-start bstr = "1.12" +gimli = "0.32" +libc = "0.2" object = "0.37" +regex = "1.11" +serde_json = "1.0" similar = "2.7" wasmparser = { version = "0.219", default-features = false, features = ["std"] } -regex = "1.11" -gimli = "0.32" +# tidy-alphabetical-end + +# Shared with bootstrap and compiletest build_helper = { path = "../../build_helper" } -serde_json = "1.0" -libc = "0.2" [lib] crate-type = ["lib", "dylib"] diff --git a/src/tools/run-make-support/src/artifact_names.rs b/src/tools/run-make-support/src/artifact_names.rs index b0d588d3550..a889b30e145 100644 --- a/src/tools/run-make-support/src/artifact_names.rs +++ b/src/tools/run-make-support/src/artifact_names.rs @@ -2,7 +2,7 @@ //! libraries which are target-dependent. use crate::target; -use crate::targets::is_msvc; +use crate::targets::is_windows_msvc; /// Construct the static library name based on the target. #[track_caller] @@ -10,7 +10,7 @@ use crate::targets::is_msvc; pub fn static_lib_name(name: &str) -> String { assert!(!name.contains(char::is_whitespace), "static library name cannot contain whitespace"); - if is_msvc() { format!("{name}.lib") } else { format!("lib{name}.a") } + if is_windows_msvc() { format!("{name}.lib") } else { format!("lib{name}.a") } } /// Construct the dynamic library name based on the target. @@ -45,7 +45,7 @@ pub fn dynamic_lib_extension() -> &'static str { #[track_caller] #[must_use] pub fn msvc_import_dynamic_lib_name(name: &str) -> String { - assert!(is_msvc(), "this function is exclusive to MSVC"); + assert!(is_windows_msvc(), "this function is exclusive to MSVC"); assert!(!name.contains(char::is_whitespace), "import library name cannot contain whitespace"); format!("{name}.dll.lib") diff --git a/src/tools/run-make-support/src/external_deps/c_build.rs b/src/tools/run-make-support/src/external_deps/c_build.rs index 9dd30713f95..ecbf5ba8fe0 100644 --- a/src/tools/run-make-support/src/external_deps/c_build.rs +++ b/src/tools/run-make-support/src/external_deps/c_build.rs @@ -4,7 +4,7 @@ use crate::artifact_names::{dynamic_lib_name, static_lib_name}; use crate::external_deps::c_cxx_compiler::{cc, cxx}; use crate::external_deps::llvm::llvm_ar; use crate::path_helpers::path; -use crate::targets::{is_darwin, is_msvc, is_windows}; +use crate::targets::{is_darwin, is_windows, is_windows_msvc}; // FIXME(Oneirical): These native build functions should take a Path-based generic. @@ -24,12 +24,12 @@ pub fn build_native_static_lib_optimized(lib_name: &str) -> PathBuf { #[track_caller] fn build_native_static_lib_internal(lib_name: &str, optimzed: bool) -> PathBuf { - let obj_file = if is_msvc() { format!("{lib_name}") } else { format!("{lib_name}.o") }; + let obj_file = if is_windows_msvc() { format!("{lib_name}") } else { format!("{lib_name}.o") }; let src = format!("{lib_name}.c"); let lib_path = static_lib_name(lib_name); let mut cc = cc(); - if !is_msvc() { + if !is_windows_msvc() { cc.arg("-v"); } if optimzed { @@ -37,7 +37,7 @@ fn build_native_static_lib_internal(lib_name: &str, optimzed: bool) -> PathBuf { } cc.arg("-c").out_exe(&obj_file).input(src).optimize().run(); - let obj_file = if is_msvc() { + let obj_file = if is_windows_msvc() { PathBuf::from(format!("{lib_name}.obj")) } else { PathBuf::from(format!("{lib_name}.o")) @@ -50,16 +50,17 @@ fn build_native_static_lib_internal(lib_name: &str, optimzed: bool) -> PathBuf { /// [`std::env::consts::DLL_PREFIX`] and [`std::env::consts::DLL_EXTENSION`]. #[track_caller] pub fn build_native_dynamic_lib(lib_name: &str) -> PathBuf { - let obj_file = if is_msvc() { format!("{lib_name}") } else { format!("{lib_name}.o") }; + let obj_file = if is_windows_msvc() { format!("{lib_name}") } else { format!("{lib_name}.o") }; let src = format!("{lib_name}.c"); let lib_path = dynamic_lib_name(lib_name); - if is_msvc() { + if is_windows_msvc() { cc().arg("-c").out_exe(&obj_file).input(src).run(); } else { cc().arg("-v").arg("-c").out_exe(&obj_file).input(src).run(); }; - let obj_file = if is_msvc() { format!("{lib_name}.obj") } else { format!("{lib_name}.o") }; - if is_msvc() { + let obj_file = + if is_windows_msvc() { format!("{lib_name}.obj") } else { format!("{lib_name}.o") }; + if is_windows_msvc() { let out_arg = format!("-out:{lib_path}"); cc().input(&obj_file).args(&["-link", "-dll", &out_arg]).run(); } else if is_darwin() { @@ -79,15 +80,15 @@ pub fn build_native_dynamic_lib(lib_name: &str) -> PathBuf { /// Built from a C++ file. #[track_caller] pub fn build_native_static_lib_cxx(lib_name: &str) -> PathBuf { - let obj_file = if is_msvc() { format!("{lib_name}") } else { format!("{lib_name}.o") }; + let obj_file = if is_windows_msvc() { format!("{lib_name}") } else { format!("{lib_name}.o") }; let src = format!("{lib_name}.cpp"); let lib_path = static_lib_name(lib_name); - if is_msvc() { + if is_windows_msvc() { cxx().arg("-EHs").arg("-c").out_exe(&obj_file).input(src).run(); } else { cxx().arg("-c").out_exe(&obj_file).input(src).run(); }; - let obj_file = if is_msvc() { + let obj_file = if is_windows_msvc() { PathBuf::from(format!("{lib_name}.obj")) } else { PathBuf::from(format!("{lib_name}.o")) diff --git a/src/tools/run-make-support/src/external_deps/c_cxx_compiler/cc.rs b/src/tools/run-make-support/src/external_deps/c_cxx_compiler/cc.rs index 0e6d6ea6075..31469e669e1 100644 --- a/src/tools/run-make-support/src/external_deps/c_cxx_compiler/cc.rs +++ b/src/tools/run-make-support/src/external_deps/c_cxx_compiler/cc.rs @@ -1,7 +1,7 @@ use std::path::Path; use crate::command::Command; -use crate::{env_var, is_msvc}; +use crate::{env_var, is_windows_msvc}; /// Construct a new platform-specific C compiler invocation. /// @@ -82,7 +82,7 @@ impl Cc { pub fn out_exe(&mut self, name: &str) -> &mut Self { let mut path = std::path::PathBuf::from(name); - if is_msvc() { + if is_windows_msvc() { path.set_extension("exe"); let fe_path = path.clone(); path.set_extension(""); @@ -108,7 +108,7 @@ impl Cc { /// Optimize the output. /// Equivalent to `-O3` for GNU-compatible linkers or `-O2` for MSVC linkers. pub fn optimize(&mut self) -> &mut Self { - if is_msvc() { + if is_windows_msvc() { self.cmd.arg("-O2"); } else { self.cmd.arg("-O3"); diff --git a/src/tools/run-make-support/src/external_deps/c_cxx_compiler/extras.rs b/src/tools/run-make-support/src/external_deps/c_cxx_compiler/extras.rs index c0317633873..ac7392641c0 100644 --- a/src/tools/run-make-support/src/external_deps/c_cxx_compiler/extras.rs +++ b/src/tools/run-make-support/src/external_deps/c_cxx_compiler/extras.rs @@ -1,9 +1,9 @@ -use crate::{is_msvc, is_win7, is_windows, uname}; +use crate::{is_win7, is_windows, is_windows_msvc, uname}; /// `EXTRACFLAGS` pub fn extra_c_flags() -> Vec<&'static str> { if is_windows() { - if is_msvc() { + if is_windows_msvc() { let mut libs = vec!["ws2_32.lib", "userenv.lib", "bcrypt.lib", "ntdll.lib", "synchronization.lib"]; if is_win7() { @@ -29,7 +29,7 @@ pub fn extra_c_flags() -> Vec<&'static str> { /// `EXTRACXXFLAGS` pub fn extra_cxx_flags() -> Vec<&'static str> { if is_windows() { - if is_msvc() { vec![] } else { vec!["-lstdc++"] } + if is_windows_msvc() { vec![] } else { vec!["-lstdc++"] } } else { match &uname()[..] { "Darwin" => vec!["-lc++"], diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs index 72a1e062a38..1ea549ca7ea 100644 --- a/src/tools/run-make-support/src/external_deps/rustc.rs +++ b/src/tools/run-make-support/src/external_deps/rustc.rs @@ -6,7 +6,7 @@ use crate::command::Command; use crate::env::env_var; use crate::path_helpers::cwd; use crate::util::set_host_compiler_dylib_path; -use crate::{is_aix, is_darwin, is_msvc, is_windows, target, uname}; +use crate::{is_aix, is_darwin, is_windows, is_windows_msvc, target, uname}; /// Construct a new `rustc` invocation. This will automatically set the library /// search path as `-L cwd()`. Use [`bare_rustc`] to avoid this. @@ -377,7 +377,7 @@ impl Rustc { // So we end up with the following hack: we link use static:-bundle to only // link the parts of libstdc++ that we actually use, which doesn't include // the dependency on the pthreads DLL. - if !is_msvc() { + if !is_windows_msvc() { self.cmd.arg("-lstatic:-bundle=stdc++"); }; } else if is_darwin() { diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs index 67d8c351a59..29cd6c4ad15 100644 --- a/src/tools/run-make-support/src/lib.rs +++ b/src/tools/run-make-support/src/lib.rs @@ -3,9 +3,6 @@ //! notably is built via cargo: this means that if your test wants some non-trivial utility, such //! as `object` or `wasmparser`, they can be re-exported and be made available through this library. -// We want to control use declaration ordering and spacing (and preserve use group comments), so -// skip rustfmt on this file. -#![cfg_attr(rustfmt, rustfmt::skip)] #![warn(unreachable_pub)] mod command; @@ -22,8 +19,8 @@ pub mod path_helpers; pub mod run; pub mod scoped_run; pub mod string; -pub mod targets; pub mod symbols; +pub mod targets; // Internally we call our fs-related support module as `fs`, but re-export its content as `rfs` // to tests to avoid colliding with commonly used `use std::fs;`. @@ -36,77 +33,56 @@ pub mod rfs { } // Re-exports of third-party library crates. -// tidy-alphabetical-start -pub use bstr; -pub use gimli; -pub use libc; -pub use object; -pub use regex; -pub use serde_json; -pub use similar; -pub use wasmparser; -// tidy-alphabetical-end +pub use {bstr, gimli, libc, object, regex, serde_json, similar, wasmparser}; -// Re-exports of external dependencies. -pub use external_deps::{ - cargo, c_build, c_cxx_compiler, clang, htmldocck, llvm, python, rustc, rustdoc +// Helpers for building names of output artifacts that are potentially target-specific. +pub use crate::artifact_names::{ + bin_name, dynamic_lib_extension, dynamic_lib_name, msvc_import_dynamic_lib_name, rust_lib_name, + static_lib_name, }; - -// These rely on external dependencies. -pub use c_cxx_compiler::{Cc, Gcc, cc, cxx, extra_c_flags, extra_cxx_flags, gcc}; -pub use c_build::{ +pub use crate::assertion_helpers::{ + assert_contains, assert_contains_regex, assert_count_is, assert_dirs_are_equal, assert_equals, + assert_not_contains, assert_not_contains_regex, +}; +// `diff` is implemented in terms of the [similar] library. +// +// [similar]: https://github.com/mitsuhiko/similar +pub use crate::diff::{Diff, diff}; +// Panic-on-fail [`std::env::var`] and [`std::env::var_os`] wrappers. +pub use crate::env::{env_var, env_var_os, set_current_dir}; +pub use crate::external_deps::c_build::{ build_native_dynamic_lib, build_native_static_lib, build_native_static_lib_cxx, build_native_static_lib_optimized, }; -pub use cargo::cargo; -pub use clang::{clang, Clang}; -pub use htmldocck::htmldocck; -pub use llvm::{ - llvm_ar, llvm_bcanalyzer, llvm_dis, llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objcopy, - llvm_objdump, llvm_profdata, llvm_readobj, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump, - LlvmFilecheck, LlvmNm, LlvmObjcopy, LlvmObjdump, LlvmProfdata, LlvmReadobj, -}; -pub use python::python_command; -pub use rustc::{bare_rustc, rustc, rustc_path, Rustc}; -pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc}; - -/// [`diff`][mod@diff] is implemented in terms of the [similar] library. -/// -/// [similar]: https://github.com/mitsuhiko/similar -pub use diff::{diff, Diff}; - -/// Panic-on-fail [`std::env::var`] and [`std::env::var_os`] wrappers. -pub use env::{env_var, env_var_os, set_current_dir}; - -/// Convenience helpers for running binaries and other commands. -pub use run::{cmd, run, run_fail, run_with_args}; - -/// Helpers for checking target information. -pub use targets::{ - apple_os, is_aix, is_darwin, is_msvc, is_windows, is_windows_gnu, is_windows_msvc, is_win7, llvm_components_contain, - target, uname, +// Re-exports of external dependencies. +pub use crate::external_deps::c_cxx_compiler::{ + Cc, Gcc, cc, cxx, extra_c_flags, extra_cxx_flags, gcc, }; - -/// Helpers for building names of output artifacts that are potentially target-specific. -pub use artifact_names::{ - bin_name, dynamic_lib_extension, dynamic_lib_name, msvc_import_dynamic_lib_name, rust_lib_name, - static_lib_name, +pub use crate::external_deps::cargo::cargo; +pub use crate::external_deps::clang::{Clang, clang}; +pub use crate::external_deps::htmldocck::htmldocck; +pub use crate::external_deps::llvm::{ + self, LlvmAr, LlvmBcanalyzer, LlvmDis, LlvmDwarfdump, LlvmFilecheck, LlvmNm, LlvmObjcopy, + LlvmObjdump, LlvmProfdata, LlvmReadobj, llvm_ar, llvm_bcanalyzer, llvm_dis, llvm_dwarfdump, + llvm_filecheck, llvm_nm, llvm_objcopy, llvm_objdump, llvm_profdata, llvm_readobj, }; - -/// Path-related helpers. -pub use path_helpers::{ +pub use crate::external_deps::python::python_command; +pub use crate::external_deps::rustc::{self, Rustc, bare_rustc, rustc, rustc_path}; +pub use crate::external_deps::rustdoc::{Rustdoc, bare_rustdoc, rustdoc}; +// Path-related helpers. +pub use crate::path_helpers::{ build_root, cwd, filename_contains, filename_not_in_denylist, has_extension, has_prefix, has_suffix, not_contains, path, shallow_find_directories, shallow_find_files, source_root, }; - -/// Helpers for scoped test execution where certain properties are attempted to be maintained. -pub use scoped_run::{run_in_tmpdir, test_while_readonly}; - -pub use assertion_helpers::{ - assert_contains, assert_contains_regex, assert_count_is, assert_dirs_are_equal, assert_equals, - assert_not_contains, assert_not_contains_regex, -}; - -pub use string::{ +// Convenience helpers for running binaries and other commands. +pub use crate::run::{cmd, run, run_fail, run_with_args}; +// Helpers for scoped test execution where certain properties are attempted to be maintained. +pub use crate::scoped_run::{run_in_tmpdir, test_while_readonly}; +pub use crate::string::{ count_regex_matches_in_files_with_extension, invalid_utf8_contains, invalid_utf8_not_contains, }; +// Helpers for checking target information. +pub use crate::targets::{ + apple_os, is_aix, is_darwin, is_win7, is_windows, is_windows_gnu, is_windows_msvc, + llvm_components_contain, target, uname, +}; diff --git a/src/tools/run-make-support/src/linker.rs b/src/tools/run-make-support/src/linker.rs index 89093cf0113..b2893ad88fe 100644 --- a/src/tools/run-make-support/src/linker.rs +++ b/src/tools/run-make-support/src/linker.rs @@ -1,6 +1,6 @@ use regex::Regex; -use crate::{Rustc, is_msvc}; +use crate::{Rustc, is_windows_msvc}; /// Asserts that `rustc` uses LLD for linking when executed. pub fn assert_rustc_uses_lld(rustc: &mut Rustc) { @@ -22,7 +22,7 @@ pub fn assert_rustc_doesnt_use_lld(rustc: &mut Rustc) { fn get_stderr_with_linker_messages(rustc: &mut Rustc) -> String { // lld-link is used if msvc, otherwise a gnu-compatible lld is used. - let linker_version_flag = if is_msvc() { "--version" } else { "-Wl,-v" }; + let linker_version_flag = if is_windows_msvc() { "--version" } else { "-Wl,-v" }; let output = rustc.arg("-Wlinker-messages").link_arg(linker_version_flag).run(); output.stderr_utf8() diff --git a/src/tools/run-make-support/src/targets.rs b/src/tools/run-make-support/src/targets.rs index 1ab2e2ab2be..b20e12561fb 100644 --- a/src/tools/run-make-support/src/targets.rs +++ b/src/tools/run-make-support/src/targets.rs @@ -16,10 +16,10 @@ pub fn is_windows() -> bool { target().contains("windows") } -/// Check if target uses msvc. +/// Check if target is windows-msvc. #[must_use] -pub fn is_msvc() -> bool { - target().contains("msvc") +pub fn is_windows_msvc() -> bool { + target().ends_with("windows-msvc") } /// Check if target is windows-gnu. @@ -28,12 +28,6 @@ pub fn is_windows_gnu() -> bool { target().ends_with("windows-gnu") } -/// Check if target is windows-msvc. -#[must_use] -pub fn is_windows_msvc() -> bool { - target().ends_with("windows-msvc") -} - /// Check if target is win7. #[must_use] pub fn is_win7() -> bool { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs index 22b7f5ac9fd..56fd12e1f2b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs @@ -489,6 +489,14 @@ impl PatCx for MatchCheckCtx<'_> { fn complexity_exceeded(&self) -> Result<(), Self::Error> { Err(()) } + + fn report_mixed_deref_pat_ctors( + &self, + _deref_pat: &DeconstructedPat<'_>, + _normal_pat: &DeconstructedPat<'_>, + ) { + // FIXME(deref_patterns): This could report an error comparable to the one in rustc. + } } impl fmt::Debug for MatchCheckCtx<'_> { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index d258c5d8191..37f83f6dee6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -25,7 +25,7 @@ impl flags::Scip { eprintln!("Generating SCIP start..."); let now = Instant::now(); - let no_progress = &|s| (eprintln!("rust-analyzer: Loading {s}")); + let no_progress = &|s| eprintln!("rust-analyzer: Loading {s}"); let root = vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(&self.path)).normalize(); diff --git a/src/tools/test-float-parse/src/lib.rs b/src/tools/test-float-parse/src/lib.rs index 0bd4878f9a6..1321a3c3354 100644 --- a/src/tools/test-float-parse/src/lib.rs +++ b/src/tools/test-float-parse/src/lib.rs @@ -340,7 +340,7 @@ fn launch_tests(tests: &mut [TestInfo], cfg: &Config) -> Duration { for test in tests.iter_mut() { test.progress = Some(ui::Progress::new(test, &mut all_progress_bars)); ui::set_panic_hook(&all_progress_bars); - ((test.launch)(test, cfg)); + (test.launch)(test, cfg); } start.elapsed() diff --git a/src/tools/tidy/src/ext_tool_checks.rs b/src/tools/tidy/src/ext_tool_checks.rs index d2da63a9703..baab46752a5 100644 --- a/src/tools/tidy/src/ext_tool_checks.rs +++ b/src/tools/tidy/src/ext_tool_checks.rs @@ -20,8 +20,11 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::process::Command; +use std::str::FromStr; use std::{fmt, fs, io}; +use crate::CiInfo; + const MIN_PY_REV: (u32, u32) = (3, 9); const MIN_PY_REV_STR: &str = "≥3.9"; @@ -36,15 +39,19 @@ const RUFF_CONFIG_PATH: &[&str] = &["src", "tools", "tidy", "config", "ruff.toml const RUFF_CACHE_PATH: &[&str] = &["cache", "ruff_cache"]; const PIP_REQ_PATH: &[&str] = &["src", "tools", "tidy", "config", "requirements.txt"]; +// this must be kept in sync with with .github/workflows/spellcheck.yml +const SPELLCHECK_DIRS: &[&str] = &["compiler", "library", "src/bootstrap", "src/librustdoc"]; + pub fn check( root_path: &Path, outdir: &Path, + ci_info: &CiInfo, bless: bool, extra_checks: Option<&str>, pos_args: &[String], bad: &mut bool, ) { - if let Err(e) = check_impl(root_path, outdir, bless, extra_checks, pos_args) { + if let Err(e) = check_impl(root_path, outdir, ci_info, bless, extra_checks, pos_args) { tidy_error!(bad, "{e}"); } } @@ -52,6 +59,7 @@ pub fn check( fn check_impl( root_path: &Path, outdir: &Path, + ci_info: &CiInfo, bless: bool, extra_checks: Option<&str>, pos_args: &[String], @@ -61,25 +69,45 @@ fn check_impl( // Split comma-separated args up let lint_args = match extra_checks { - Some(s) => s.strip_prefix("--extra-checks=").unwrap().split(',').collect(), + Some(s) => s + .strip_prefix("--extra-checks=") + .unwrap() + .split(',') + .map(|s| { + if s == "spellcheck:fix" { + eprintln!("warning: `spellcheck:fix` is no longer valid, use `--extra-checks=spellcheck --bless`"); + } + (ExtraCheckArg::from_str(s), s) + }) + .filter_map(|(res, src)| match res { + Ok(arg) => { + if arg.is_inactive_auto(ci_info) { + None + } else { + Some(arg) + } + } + Err(err) => { + // only warn because before bad extra checks would be silently ignored. + eprintln!("warning: bad extra check argument {src:?}: {err:?}"); + None + } + }) + .collect(), None => vec![], }; - if lint_args.contains(&"spellcheck:fix") { - return Err(Error::Generic( - "`spellcheck:fix` is no longer valid, use `--extra=check=spellcheck --bless`" - .to_string(), - )); + macro_rules! extra_check { + ($lang:ident, $kind:ident) => { + lint_args.iter().any(|arg| arg.matches(ExtraCheckLang::$lang, ExtraCheckKind::$kind)) + }; } - let python_all = lint_args.contains(&"py"); - let python_lint = lint_args.contains(&"py:lint") || python_all; - let python_fmt = lint_args.contains(&"py:fmt") || python_all; - let shell_all = lint_args.contains(&"shell"); - let shell_lint = lint_args.contains(&"shell:lint") || shell_all; - let cpp_all = lint_args.contains(&"cpp"); - let cpp_fmt = lint_args.contains(&"cpp:fmt") || cpp_all; - let spellcheck = lint_args.contains(&"spellcheck"); + let python_lint = extra_check!(Py, Lint); + let python_fmt = extra_check!(Py, Fmt); + let shell_lint = extra_check!(Shell, Lint); + let cpp_fmt = extra_check!(Cpp, Fmt); + let spellcheck = extra_check!(Spellcheck, None); let mut py_path = None; @@ -234,15 +262,9 @@ fn check_impl( if spellcheck { let config_path = root_path.join("typos.toml"); - // sync target files with .github/workflows/spellcheck.yml - let mut args = vec![ - "-c", - config_path.as_os_str().to_str().unwrap(), - "./compiler", - "./library", - "./src/bootstrap", - "./src/librustdoc", - ]; + let mut args = vec!["-c", config_path.as_os_str().to_str().unwrap()]; + + args.extend_from_slice(SPELLCHECK_DIRS); if bless { eprintln!("spellcheck files and fix"); @@ -638,3 +660,136 @@ impl From<io::Error> for Error { Self::Io(value) } } + +#[derive(Debug)] +enum ExtraCheckParseError { + #[allow(dead_code, reason = "shown through Debug")] + UnknownKind(String), + #[allow(dead_code)] + UnknownLang(String), + UnsupportedKindForLang, + /// Too many `:` + TooManyParts, + /// Tried to parse the empty string + Empty, + /// `auto` specified without lang part. + AutoRequiresLang, +} + +struct ExtraCheckArg { + auto: bool, + lang: ExtraCheckLang, + /// None = run all extra checks for the given lang + kind: Option<ExtraCheckKind>, +} + +impl ExtraCheckArg { + fn matches(&self, lang: ExtraCheckLang, kind: ExtraCheckKind) -> bool { + self.lang == lang && self.kind.map(|k| k == kind).unwrap_or(true) + } + + /// Returns `true` if this is an auto arg and the relevant files are not modified. + fn is_inactive_auto(&self, ci_info: &CiInfo) -> bool { + if !self.auto { + return false; + } + let ext = match self.lang { + ExtraCheckLang::Py => ".py", + ExtraCheckLang::Cpp => ".cpp", + ExtraCheckLang::Shell => ".sh", + ExtraCheckLang::Spellcheck => { + return !crate::files_modified(ci_info, |s| { + SPELLCHECK_DIRS.iter().any(|dir| Path::new(s).starts_with(dir)) + }); + } + }; + !crate::files_modified(ci_info, |s| s.ends_with(ext)) + } + + fn has_supported_kind(&self) -> bool { + let Some(kind) = self.kind else { + // "run all extra checks" mode is supported for all languages. + return true; + }; + use ExtraCheckKind::*; + let supported_kinds: &[_] = match self.lang { + ExtraCheckLang::Py => &[Fmt, Lint], + ExtraCheckLang::Cpp => &[Fmt], + ExtraCheckLang::Shell => &[Lint], + ExtraCheckLang::Spellcheck => &[], + }; + supported_kinds.contains(&kind) + } +} + +impl FromStr for ExtraCheckArg { + type Err = ExtraCheckParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut auto = false; + let mut parts = s.split(':'); + let Some(mut first) = parts.next() else { + return Err(ExtraCheckParseError::Empty); + }; + if first == "auto" { + let Some(part) = parts.next() else { + return Err(ExtraCheckParseError::AutoRequiresLang); + }; + auto = true; + first = part; + } + let second = parts.next(); + if parts.next().is_some() { + return Err(ExtraCheckParseError::TooManyParts); + } + let arg = Self { auto, lang: first.parse()?, kind: second.map(|s| s.parse()).transpose()? }; + if !arg.has_supported_kind() { + return Err(ExtraCheckParseError::UnsupportedKindForLang); + } + + Ok(arg) + } +} + +#[derive(PartialEq, Copy, Clone)] +enum ExtraCheckLang { + Py, + Shell, + Cpp, + Spellcheck, +} + +impl FromStr for ExtraCheckLang { + type Err = ExtraCheckParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s { + "py" => Self::Py, + "shell" => Self::Shell, + "cpp" => Self::Cpp, + "spellcheck" => Self::Spellcheck, + _ => return Err(ExtraCheckParseError::UnknownLang(s.to_string())), + }) + } +} + +#[derive(PartialEq, Copy, Clone)] +enum ExtraCheckKind { + Lint, + Fmt, + /// Never parsed, but used as a placeholder for + /// langs that never have a specific kind. + None, +} + +impl FromStr for ExtraCheckKind { + type Err = ExtraCheckParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s { + "lint" => Self::Lint, + "fmt" => Self::Fmt, + _ => return Err(ExtraCheckParseError::UnknownKind(s.to_string())), + }) + } +} diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index 237737f0f16..77855392b4d 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -124,6 +124,40 @@ pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<Stri Some(String::from_utf8_lossy(&output.stdout).into()) } +/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files. +pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool { + if CiEnv::is_ci() { + // assume everything is modified on CI because we really don't want false positives there. + return true; + } + let Some(base_commit) = &ci_info.base_commit else { + eprintln!("No base commit, assuming all files are modified"); + return true; + }; + match crate::git_diff(&base_commit, "--name-status") { + Some(output) => { + let modified_files = output.lines().filter_map(|ln| { + let (status, name) = ln + .trim_end() + .split_once('\t') + .expect("bad format from `git diff --name-status`"); + if status == "M" { Some(name) } else { None } + }); + for modified_file in modified_files { + if pred(modified_file) { + return true; + } + } + false + } + None => { + eprintln!("warning: failed to run `git diff` to check for changes"); + eprintln!("warning: assuming all files are modified"); + true + } + } +} + pub mod alphabetical; pub mod bins; pub mod debug_artifacts; diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs index ef6ff5c9277..1eb5485f2b8 100644 --- a/src/tools/tidy/src/main.rs +++ b/src/tools/tidy/src/main.rs @@ -173,7 +173,15 @@ fn main() { }; check!(unstable_book, &src_path, collected); - check!(ext_tool_checks, &root_path, &output_directory, bless, extra_checks, pos_args); + check!( + ext_tool_checks, + &root_path, + &output_directory, + &ci_info, + bless, + extra_checks, + pos_args + ); }); if bad.load(Ordering::Relaxed) { diff --git a/src/tools/tidy/src/rustdoc_json.rs b/src/tools/tidy/src/rustdoc_json.rs index dfbb35d69f1..19b8e79ec33 100644 --- a/src/tools/tidy/src/rustdoc_json.rs +++ b/src/tools/tidy/src/rustdoc_json.rs @@ -14,22 +14,10 @@ pub fn check(src_path: &Path, ci_info: &crate::CiInfo, bad: &mut bool) { }; // First we check that `src/rustdoc-json-types` was modified. - match crate::git_diff(&base_commit, "--name-status") { - Some(output) => { - if !output - .lines() - .any(|line| line.starts_with("M") && line.contains(RUSTDOC_JSON_TYPES)) - { - // `rustdoc-json-types` was not modified so nothing more to check here. - println!("`rustdoc-json-types` was not modified."); - return; - } - } - None => { - *bad = true; - eprintln!("error: failed to run `git diff` in rustdoc_json check"); - return; - } + if !crate::files_modified(ci_info, |p| p == RUSTDOC_JSON_TYPES) { + // `rustdoc-json-types` was not modified so nothing more to check here. + println!("`rustdoc-json-types` was not modified."); + return; } // Then we check that if `FORMAT_VERSION` was updated, the `Latest feature:` was also updated. match crate::git_diff(&base_commit, src_path.join("rustdoc-json-types")) { diff --git a/src/tools/unicode-table-generator/src/range_search.rs b/src/tools/unicode-table-generator/src/range_search.rs index 02f9cf16d4d..4d1dd9b423b 100644 --- a/src/tools/unicode-table-generator/src/range_search.rs +++ b/src/tools/unicode-table-generator/src/range_search.rs @@ -80,7 +80,7 @@ unsafe fn skip_search<const SOR: usize, const OFFSETS: usize>( let needle = needle as u32; let last_idx = - match short_offset_runs.binary_search_by_key(&(needle << 11), |header| (header.0 << 11)) { + match short_offset_runs.binary_search_by_key(&(needle << 11), |header| header.0 << 11) { Ok(idx) => idx + 1, Err(idx) => idx, }; |
