diff --git a/.gitmodules b/.gitmodules index b2b580d08bc28..5f2e8272cd2f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,9 +16,6 @@ [submodule "src/tools/rls"] path = src/tools/rls url = https://github.com/rust-lang/rls.git -[submodule "src/tools/clippy"] - path = src/tools/clippy - url = https://github.com/rust-lang/rust-clippy.git [submodule "src/tools/rustfmt"] path = src/tools/rustfmt url = https://github.com/rust-lang/rustfmt.git diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index c0018c613ba5c..a897daf446de7 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -351,7 +351,7 @@ impl<'a> Builder<'a> { native::Lld ), Kind::Check | Kind::Clippy | Kind::Fix | Kind::Format => { - describe!(check::Std, check::Rustc, check::Rustdoc) + describe!(check::Std, check::Rustc, check::Rustdoc, check::Clippy) } Kind::Test => describe!( crate::toolstate::ToolStateCheck, diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index 586a362b5e3fe..7a8bfb2d5d877 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -112,83 +112,89 @@ impl Step for Rustc { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct Rustdoc { - pub target: Interned, +macro_rules! tool_check_step { + ($name:ident, $path:expr) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct $name { + pub target: Interned, + } + + impl Step for $name { + type Output = (); + const ONLY_HOSTS: bool = true; + const DEFAULT: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path($path) + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure($name { target: run.target }); + } + + fn run(self, builder: &Builder<'_>) { + let compiler = builder.compiler(0, builder.config.build); + let target = self.target; + + builder.ensure(Rustc { target }); + + let cargo = prepare_tool_cargo( + builder, + compiler, + Mode::ToolRustc, + target, + cargo_subcommand(builder.kind), + $path, + SourceType::InTree, + &[], + ); + + println!( + "Checking {} artifacts ({} -> {})", + stringify!($name).to_lowercase(), + &compiler.host, + target + ); + run_cargo( + builder, + cargo, + args(builder.kind), + &stamp(builder, compiler, target), + vec![], + true, + ); + + let libdir = builder.sysroot_libdir(compiler, target); + let hostdir = builder.sysroot_libdir(compiler, compiler.host); + add_to_sysroot(&builder, &libdir, &hostdir, &stamp(builder, compiler, target)); + + /// Cargo's output path in a given stage, compiled by a particular + /// compiler for the specified target. + fn stamp( + builder: &Builder<'_>, + compiler: Compiler, + target: Interned, + ) -> PathBuf { + builder + .cargo_out(compiler, Mode::ToolRustc, target) + .join(format!(".{}-check.stamp", stringify!($name).to_lowercase())) + } + } + } + }; } -impl Step for Rustdoc { - type Output = (); - const ONLY_HOSTS: bool = true; - const DEFAULT: bool = true; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path("src/tools/rustdoc") - } - - fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Rustdoc { target: run.target }); - } - - fn run(self, builder: &Builder<'_>) { - let compiler = builder.compiler(0, builder.config.build); - let target = self.target; - - builder.ensure(Rustc { target }); - - let cargo = prepare_tool_cargo( - builder, - compiler, - Mode::ToolRustc, - target, - cargo_subcommand(builder.kind), - "src/tools/rustdoc", - SourceType::InTree, - &[], - ); - - println!("Checking rustdoc artifacts ({} -> {})", &compiler.host, target); - run_cargo( - builder, - cargo, - args(builder.kind), - &rustdoc_stamp(builder, compiler, target), - vec![], - true, - ); - - let libdir = builder.sysroot_libdir(compiler, target); - let hostdir = builder.sysroot_libdir(compiler, compiler.host); - add_to_sysroot(&builder, &libdir, &hostdir, &rustdoc_stamp(builder, compiler, target)); - } -} +tool_check_step!(Rustdoc, "src/tools/rustdoc"); +tool_check_step!(Clippy, "src/tools/clippy"); /// Cargo's output path for the standard library in a given stage, compiled /// by a particular compiler for the specified target. -pub fn libstd_stamp( - builder: &Builder<'_>, - compiler: Compiler, - target: Interned, -) -> PathBuf { +fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: Interned) -> PathBuf { builder.cargo_out(compiler, Mode::Std, target).join(".libstd-check.stamp") } /// Cargo's output path for librustc in a given stage, compiled by a particular /// compiler for the specified target. -pub fn librustc_stamp( - builder: &Builder<'_>, - compiler: Compiler, - target: Interned, -) -> PathBuf { +fn librustc_stamp(builder: &Builder<'_>, compiler: Compiler, target: Interned) -> PathBuf { builder.cargo_out(compiler, Mode::Rustc, target).join(".librustc-check.stamp") } - -/// Cargo's output path for rustdoc in a given stage, compiled by a particular -/// compiler for the specified target. -pub fn rustdoc_stamp( - builder: &Builder<'_>, - compiler: Compiler, - target: Interned, -) -> PathBuf { - builder.cargo_out(compiler, Mode::ToolRustc, target).join(".rustdoc-check.stamp") -} diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 8215211ea1c9d..bae904114960a 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -1330,7 +1330,7 @@ pub struct Clippy { } impl Step for Clippy { - type Output = Option; + type Output = PathBuf; const ONLY_HOSTS: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -1348,7 +1348,7 @@ impl Step for Clippy { }); } - fn run(self, builder: &Builder<'_>) -> Option { + fn run(self, builder: &Builder<'_>) -> PathBuf { let compiler = self.compiler; let target = self.target; assert!(builder.config.extended); @@ -1368,16 +1368,10 @@ impl Step for Clippy { // state for clippy isn't testing. let clippy = builder .ensure(tool::Clippy { compiler, target, extra_features: Vec::new() }) - .or_else(|| { - missing_tool("clippy", builder.build.config.missing_tools); - None - })?; + .expect("clippy expected to build - essential tool"); let cargoclippy = builder .ensure(tool::CargoClippy { compiler, target, extra_features: Vec::new() }) - .or_else(|| { - missing_tool("cargo clippy", builder.build.config.missing_tools); - None - })?; + .expect("clippy expected to build - essential tool"); builder.install(&clippy, &image.join("bin"), 0o755); builder.install(&cargoclippy, &image.join("bin"), 0o755); @@ -1416,7 +1410,7 @@ impl Step for Clippy { builder.info(&format!("Dist clippy stage{} ({})", compiler.stage, target)); let _time = timeit(builder); builder.run(&mut cmd); - Some(distdir(builder).join(format!("{}-{}.tar.gz", name, target))) + distdir(builder).join(format!("{}-{}.tar.gz", name, target)) } } @@ -1683,7 +1677,7 @@ impl Step for Extended { tarballs.push(rustc_installer); tarballs.push(cargo_installer); tarballs.extend(rls_installer.clone()); - tarballs.extend(clippy_installer.clone()); + tarballs.push(clippy_installer); tarballs.extend(miri_installer.clone()); tarballs.extend(rustfmt_installer.clone()); tarballs.extend(llvm_tools_installer); @@ -1761,9 +1755,6 @@ impl Step for Extended { if rls_installer.is_none() { contents = filter(&contents, "rls"); } - if clippy_installer.is_none() { - contents = filter(&contents, "clippy"); - } if miri_installer.is_none() { contents = filter(&contents, "miri"); } @@ -1805,13 +1796,11 @@ impl Step for Extended { prepare("rust-docs"); prepare("rust-std"); prepare("rust-analysis"); + prepare("clippy"); if rls_installer.is_some() { prepare("rls"); } - if clippy_installer.is_some() { - prepare("clippy"); - } if miri_installer.is_some() { prepare("miri"); } @@ -1863,12 +1852,10 @@ impl Step for Extended { prepare("rust-analysis"); prepare("rust-docs"); prepare("rust-std"); + prepare("clippy"); if rls_installer.is_some() { prepare("rls"); } - if clippy_installer.is_some() { - prepare("clippy"); - } if miri_installer.is_some() { prepare("miri"); } @@ -1989,25 +1976,23 @@ impl Step for Extended { .arg(etc.join("msi/remove-duplicates.xsl")), ); } - if clippy_installer.is_some() { - builder.run( - Command::new(&heat) - .current_dir(&exe) - .arg("dir") - .arg("clippy") - .args(&heat_flags) - .arg("-cg") - .arg("ClippyGroup") - .arg("-dr") - .arg("Clippy") - .arg("-var") - .arg("var.ClippyDir") - .arg("-out") - .arg(exe.join("ClippyGroup.wxs")) - .arg("-t") - .arg(etc.join("msi/remove-duplicates.xsl")), - ); - } + builder.run( + Command::new(&heat) + .current_dir(&exe) + .arg("dir") + .arg("clippy") + .args(&heat_flags) + .arg("-cg") + .arg("ClippyGroup") + .arg("-dr") + .arg("Clippy") + .arg("-var") + .arg("var.ClippyDir") + .arg("-out") + .arg(exe.join("ClippyGroup.wxs")) + .arg("-t") + .arg(etc.join("msi/remove-duplicates.xsl")), + ); if miri_installer.is_some() { builder.run( Command::new(&heat) @@ -2073,6 +2058,7 @@ impl Step for Extended { .arg("-dCargoDir=cargo") .arg("-dStdDir=rust-std") .arg("-dAnalysisDir=rust-analysis") + .arg("-dClippyDir=clippy") .arg("-arch") .arg(&arch) .arg("-out") @@ -2083,9 +2069,6 @@ impl Step for Extended { if rls_installer.is_some() { cmd.arg("-dRlsDir=rls"); } - if clippy_installer.is_some() { - cmd.arg("-dClippyDir=clippy"); - } if miri_installer.is_some() { cmd.arg("-dMiriDir=miri"); } @@ -2101,12 +2084,10 @@ impl Step for Extended { candle("DocsGroup.wxs".as_ref()); candle("CargoGroup.wxs".as_ref()); candle("StdGroup.wxs".as_ref()); + candle("ClippyGroup.wxs".as_ref()); if rls_installer.is_some() { candle("RlsGroup.wxs".as_ref()); } - if clippy_installer.is_some() { - candle("ClippyGroup.wxs".as_ref()); - } if miri_installer.is_some() { candle("MiriGroup.wxs".as_ref()); } @@ -2138,14 +2119,12 @@ impl Step for Extended { .arg("CargoGroup.wixobj") .arg("StdGroup.wixobj") .arg("AnalysisGroup.wixobj") + .arg("ClippyGroup.wixobj") .current_dir(&exe); if rls_installer.is_some() { cmd.arg("RlsGroup.wixobj"); } - if clippy_installer.is_some() { - cmd.arg("ClippyGroup.wixobj"); - } if miri_installer.is_some() { cmd.arg("MiriGroup.wixobj"); } diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs index 6549262811b9f..fafd3cdf927c0 100644 --- a/src/bootstrap/install.rs +++ b/src/bootstrap/install.rs @@ -214,10 +214,8 @@ install!((self, builder, _config), } }; Clippy, "clippy", Self::should_build(_config), only_hosts: true, { - if builder.ensure(dist::Clippy { - compiler: self.compiler, - target: self.target, - }).is_some() || Self::should_install(builder) { + builder.ensure(dist::Clippy { compiler: self.compiler, target: self.target }); + if Self::should_install(builder) { install_clippy(builder, self.compiler.stage, self.target); } else { builder.info( diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 125563b7b6086..90b8b5eea947f 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -548,9 +548,7 @@ impl Step for Clippy { builder.add_rustc_lib_path(compiler, &mut cargo); - if try_run(builder, &mut cargo.into()) { - builder.save_toolstate("clippy-driver", ToolState::TestPass); - } + try_run(builder, &mut cargo.into()); } else { eprintln!("failed to test clippy: could not build"); } diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index f756b1235abee..8ebad95ea1762 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -652,14 +652,12 @@ tool_extended!((self, builder), Miri, miri, "src/tools/miri", "miri", {}; CargoMiri, miri, "src/tools/miri", "cargo-miri", {}; Rls, rls, "src/tools/rls", "rls", { - let clippy = builder.ensure(Clippy { + builder.ensure(Clippy { compiler: self.compiler, target: self.target, extra_features: Vec::new(), }); - if clippy.is_some() { - self.extra_features.push("clippy".to_owned()); - } + self.extra_features.push("clippy".to_owned()); }; Rustfmt, rustfmt, "src/tools/rustfmt", "rustfmt", {}; ); diff --git a/src/tools/clippy b/src/tools/clippy deleted file mode 160000 index 28197b622611b..0000000000000 --- a/src/tools/clippy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 28197b622611ba3a6367648974ccf59127c287bb diff --git a/src/tools/clippy/.cargo/config b/src/tools/clippy/.cargo/config new file mode 100644 index 0000000000000..2bad3b9c57f0c --- /dev/null +++ b/src/tools/clippy/.cargo/config @@ -0,0 +1,6 @@ +[alias] +uitest = "test --test compile-test" +dev = "run --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --" + +[build] +rustflags = ["-Zunstable-options"] diff --git a/src/tools/clippy/.editorconfig b/src/tools/clippy/.editorconfig new file mode 100644 index 0000000000000..a13173544d80a --- /dev/null +++ b/src/tools/clippy/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/src/tools/clippy/.gitattributes b/src/tools/clippy/.gitattributes new file mode 100644 index 0000000000000..90cf33053c775 --- /dev/null +++ b/src/tools/clippy/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.rs text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4 +*.fixed linguist-language=Rust diff --git a/src/tools/clippy/.github/ISSUE_TEMPLATE.md b/src/tools/clippy/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000000..15006a07b44f2 --- /dev/null +++ b/src/tools/clippy/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,8 @@ + diff --git a/src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md b/src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000..97aa220afea54 --- /dev/null +++ b/src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +Thank you for making Clippy better! + +We're collecting our changelog from pull request descriptions. +If your PR only updates to the latest nightly, you can leave the +`changelog` entry as `none`. Otherwise, please write a short comment +explaining your change. + +If your PR fixes an issue, you can add "fixes #issue_number" into this +PR description. This way the issue will be automatically closed when +your PR is merged. + +If you added a new lint, here's a checklist for things that will be +checked during review or continuous integration. + +- [ ] Followed [lint naming conventions][lint_naming] +- [ ] Added passing UI tests (including committed `.stderr` file) +- [ ] `cargo test` passes locally +- [ ] Executed `cargo dev update_lints` +- [ ] Added lint documentation +- [ ] Run `cargo dev fmt` + +[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints + +Note that you can skip the above if you are just opening a WIP PR in +order to get feedback. + +Delete this line and everything above before opening your PR. + +--- + +changelog: none diff --git a/src/tools/clippy/.github/deploy.sh b/src/tools/clippy/.github/deploy.sh new file mode 100644 index 0000000000000..3f425e5b7258d --- /dev/null +++ b/src/tools/clippy/.github/deploy.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -ex + +echo "Removing the current docs for master" +rm -rf out/master/ || exit 0 + +echo "Making the docs for master" +mkdir out/master/ +cp util/gh-pages/index.html out/master +python3 ./util/export.py out/master/lints.json + +if [[ -n $TAG_NAME ]]; then + echo "Save the doc for the current tag ($TAG_NAME) and point stable/ to it" + cp -r out/master "out/$TAG_NAME" + rm -f out/stable + ln -s "$TAG_NAME" out/stable +fi + +if [[ $BETA = "true" ]]; then + echo "Update documentation for the beta release" + cp -r out/master out/beta +fi + +# Generate version index that is shown as root index page +cp util/gh-pages/versions.html out/index.html + +echo "Making the versions.json file" +python3 ./util/versions.py out + +cd out +# Now let's go have some fun with the cloned repo +git config user.name "GHA CI" +git config user.email "gha@ci.invalid" + +if git diff --exit-code --quiet; then + echo "No changes to the output on this push; exiting." + exit 0 +fi + +if [[ -n $TAG_NAME ]]; then + # Add the new dir + git add "$TAG_NAME" + # Update the symlink + git add stable + # Update versions file + git add versions.json + git commit -m "Add documentation for ${TAG_NAME} release: ${SHA}" +elif [[ $BETA = "true" ]]; then + git add beta + git commit -m "Automatic deploy to GitHub Pages (beta): ${SHA}" +else + git add . + git commit -m "Automatic deploy to GitHub Pages: ${SHA}" +fi + +git push "$SSH_REPO" "$TARGET_BRANCH" diff --git a/src/tools/clippy/.github/driver.sh b/src/tools/clippy/.github/driver.sh new file mode 100644 index 0000000000000..a2e87f5eb3745 --- /dev/null +++ b/src/tools/clippy/.github/driver.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -ex + +# Check sysroot handling +sysroot=$(./target/debug/clippy-driver --print sysroot) +test "$sysroot" = "$(rustc --print sysroot)" + +if [[ ${OS} == "Windows" ]]; then + desired_sysroot=C:/tmp +else + desired_sysroot=/tmp +fi +sysroot=$(./target/debug/clippy-driver --sysroot $desired_sysroot --print sysroot) +test "$sysroot" = $desired_sysroot + +sysroot=$(SYSROOT=$desired_sysroot ./target/debug/clippy-driver --print sysroot) +test "$sysroot" = $desired_sysroot + +# Make sure this isn't set - clippy-driver should cope without it +unset CARGO_MANIFEST_DIR + +# Run a lint and make sure it produces the expected output. It's also expected to exit with code 1 +# FIXME: How to match the clippy invocation in compile-test.rs? +./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/cstring.rs 2> cstring.stderr && exit 1 +sed -e "s,tests/ui,\$DIR," -e "/= help/d" cstring.stderr > normalized.stderr +diff normalized.stderr tests/ui/cstring.stderr + +# TODO: CLIPPY_CONF_DIR / CARGO_MANIFEST_DIR diff --git a/src/tools/clippy/.github/workflows/clippy.yml b/src/tools/clippy/.github/workflows/clippy.yml new file mode 100644 index 0000000000000..8edf0c23860aa --- /dev/null +++ b/src/tools/clippy/.github/workflows/clippy.yml @@ -0,0 +1,99 @@ +name: Clippy Test + +on: + push: + # Ignore bors branches, since they are covered by `clippy_bors.yml` + branches-ignore: + - auto + - try + # Don't run Clippy tests, when only textfiles were modified + paths-ignore: + - 'COPYRIGHT' + - 'LICENSE-*' + - '**.md' + - '**.txt' + pull_request: + # Don't run Clippy tests, when only textfiles were modified + paths-ignore: + - 'COPYRIGHT' + - 'LICENSE-*' + - '**.md' + - '**.txt' + +env: + RUST_BACKTRACE: 1 + CARGO_TARGET_DIR: '${{ github.workspace }}/target' + NO_FMT_TEST: 1 + +jobs: + base: + runs-on: ubuntu-latest + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: rust-toolchain + uses: actions-rs/toolchain@v1.0.3 + with: + toolchain: nightly + target: x86_64-unknown-linux-gnu + profile: minimal + + - name: Checkout + uses: actions/checkout@v2.0.0 + + - name: Run cargo update + run: cargo update + + - name: Cache cargo dir + uses: actions/cache@v1 + with: + path: ~/.cargo + key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-x86_64-unknown-linux-gnu + + - name: Master Toolchain Setup + run: bash setup-toolchain.sh + + # Run + - name: Set LD_LIBRARY_PATH (Linux) + run: | + SYSROOT=$(rustc --print sysroot) + echo "::set-env name=LD_LIBRARY_PATH::${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}" + + - name: Build + run: cargo build --features deny-warnings + + - name: Test + run: cargo test --features deny-warnings + + - name: Test clippy_lints + run: cargo test --features deny-warnings + working-directory: clippy_lints + + - name: Test rustc_tools_util + run: cargo test --features deny-warnings + working-directory: rustc_tools_util + + - name: Test clippy_dev + run: cargo test --features deny-warnings + working-directory: clippy_dev + + - name: Test cargo-clippy + run: ../target/debug/cargo-clippy + working-directory: clippy_workspace_tests + + - name: Test clippy-driver + run: bash .github/driver.sh + env: + OS: ${{ runner.os }} + + # Cleanup + - name: Run cargo-cache --autoclean + run: | + cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache + cargo cache diff --git a/src/tools/clippy/.github/workflows/clippy_bors.yml b/src/tools/clippy/.github/workflows/clippy_bors.yml new file mode 100644 index 0000000000000..6675a1029bbc8 --- /dev/null +++ b/src/tools/clippy/.github/workflows/clippy_bors.yml @@ -0,0 +1,329 @@ +name: Clippy Test (bors) + +on: + push: + branches: + - auto + - try + +env: + RUST_BACKTRACE: 1 + CARGO_TARGET_DIR: '${{ github.workspace }}/target' + NO_FMT_TEST: 1 + +jobs: + changelog: + runs-on: ubuntu-latest + + steps: + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + - name: Checkout + uses: actions/checkout@v2.0.0 + with: + ref: ${{ github.ref }} + + # Run + - name: Check Changelog + run: | + MESSAGE=$(git log --format=%B -n 1) + PR=$(echo "$MESSAGE" | grep -o "#[0-9]*" | head -1 | sed -e 's/^#//') + output=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR" | \ + python -c "import sys, json; print(json.load(sys.stdin)['body'])" | \ + grep "^changelog: " | \ + sed "s/changelog: //g") + if [[ -z "$output" ]]; then + echo "ERROR: PR body must contain 'changelog: ...'" + exit 1 + elif [[ "$output" = "none" ]]; then + echo "WARNING: changelog is 'none'" + fi + env: + PYTHONIOENCODING: 'utf-8' + base: + needs: changelog + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + host: [x86_64-unknown-linux-gnu, i686-unknown-linux-gnu, x86_64-apple-darwin, x86_64-pc-windows-msvc] + exclude: + - os: ubuntu-latest + host: x86_64-apple-darwin + - os: ubuntu-latest + host: x86_64-pc-windows-msvc + - os: macos-latest + host: x86_64-unknown-linux-gnu + - os: macos-latest + host: i686-unknown-linux-gnu + - os: macos-latest + host: x86_64-pc-windows-msvc + - os: windows-latest + host: x86_64-unknown-linux-gnu + - os: windows-latest + host: i686-unknown-linux-gnu + - os: windows-latest + host: x86_64-apple-darwin + + runs-on: ${{ matrix.os }} + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: Install dependencies (Linux-i686) + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install gcc-multilib libssl-dev:i386 libgit2-dev:i386 + if: matrix.host == 'i686-unknown-linux-gnu' + + - name: rust-toolchain + uses: actions-rs/toolchain@v1.0.3 + with: + toolchain: nightly + target: ${{ matrix.host }} + profile: minimal + + - name: Checkout + uses: actions/checkout@v2.0.0 + + - name: Run cargo update + run: cargo update + + - name: Cache cargo dir + uses: actions/cache@v1 + with: + path: ~/.cargo + key: ${{ runner.os }}-${{ matrix.host }}-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.host }} + + - name: Master Toolchain Setup + run: bash setup-toolchain.sh + env: + HOST_TOOLCHAIN: ${{ matrix.host }} + shell: bash + + # Run + - name: Set LD_LIBRARY_PATH (Linux) + if: runner.os == 'Linux' + run: | + SYSROOT=$(rustc --print sysroot) + echo "::set-env name=LD_LIBRARY_PATH::${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}" + - name: Link rustc dylib (MacOS) + if: runner.os == 'macOS' + run: | + SYSROOT=$(rustc --print sysroot) + sudo mkdir -p /usr/local/lib + sudo find "${SYSROOT}/lib" -maxdepth 1 -name '*dylib' -exec ln -s {} /usr/local/lib \; + - name: Set PATH (Windows) + if: runner.os == 'Windows' + run: | + $sysroot = rustc --print sysroot + $env:PATH += ';' + $sysroot + '\bin' + echo "::set-env name=PATH::$env:PATH" + + - name: Build + run: cargo build --features deny-warnings + shell: bash + + - name: Test + run: cargo test --features deny-warnings + shell: bash + + - name: Test clippy_lints + run: cargo test --features deny-warnings + shell: bash + working-directory: clippy_lints + + - name: Test rustc_tools_util + run: cargo test --features deny-warnings + shell: bash + working-directory: rustc_tools_util + + - name: Test clippy_dev + run: cargo test --features deny-warnings + shell: bash + working-directory: clippy_dev + + - name: Test cargo-clippy + run: ../target/debug/cargo-clippy + shell: bash + working-directory: clippy_workspace_tests + + - name: Test clippy-driver + run: bash .github/driver.sh + shell: bash + env: + OS: ${{ runner.os }} + + # Cleanup + - name: Run cargo-cache --autoclean + run: | + cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache + cargo cache + shell: bash + integration_build: + needs: changelog + runs-on: ubuntu-latest + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: rust-toolchain + uses: actions-rs/toolchain@v1.0.3 + with: + toolchain: nightly + target: x86_64-unknown-linux-gnu + profile: minimal + + - name: Checkout + uses: actions/checkout@v2.0.0 + + - name: Run cargo update + run: cargo update + + - name: Cache cargo dir + uses: actions/cache@v1 + with: + path: ~/.cargo + key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-x86_64-unknown-linux-gnu + + - name: Master Toolchain Setup + run: bash setup-toolchain.sh + + # Run + - name: Build Integration Test + run: cargo test --test integration --features integration --no-run + + # Upload + - name: Extract Binaries + run: | + DIR=$CARGO_TARGET_DIR/debug + rm $DIR/deps/integration-*.d + mv $DIR/deps/integration-* $DIR/integration + find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf + rm -rf $CARGO_TARGET_DIR/release + + - name: Upload Binaries + uses: actions/upload-artifact@v1 + with: + name: target + path: target + + # Cleanup + - name: Run cargo-cache --autoclean + run: | + cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache + cargo cache + integration: + needs: integration_build + strategy: + fail-fast: false + max-parallel: 6 + matrix: + integration: + - 'rust-lang/cargo' + - 'rust-lang/rls' + - 'rust-lang/chalk' + - 'rust-lang/rustfmt' + - 'Marwes/combine' + - 'Geal/nom' + - 'rust-lang/stdarch' + - 'serde-rs/serde' + - 'chronotope/chrono' + - 'hyperium/hyper' + - 'rust-random/rand' + - 'rust-lang/futures-rs' + - 'rust-itertools/itertools' + - 'rust-lang-nursery/failure' + - 'rust-lang/log' + + runs-on: ubuntu-latest + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: rust-toolchain + uses: actions-rs/toolchain@v1.0.3 + with: + toolchain: nightly + target: x86_64-unknown-linux-gnu + profile: minimal + + - name: Checkout + uses: actions/checkout@v2.0.0 + + - name: Run cargo update + run: cargo update + + - name: Cache cargo dir + uses: actions/cache@v1 + with: + path: ~/.cargo + key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-x86_64-unknown-linux-gnu + + - name: Master Toolchain Setup + run: bash setup-toolchain.sh + + # Download + - name: Download target dir + uses: actions/download-artifact@v1 + with: + name: target + path: target + + - name: Make Binaries Executable + run: chmod +x $CARGO_TARGET_DIR/debug/* + + # Run + - name: Test ${{ matrix.integration }} + run: $CARGO_TARGET_DIR/debug/integration + env: + INTEGRATION: ${{ matrix.integration }} + RUSTUP_TOOLCHAIN: master + + # Cleanup + - name: Run cargo-cache --autoclean + run: | + cargo +nightly install cargo-cache --no-default-features --features ci-autoclean cargo-cache + cargo cache + + # These jobs doesn't actually test anything, but they're only used to tell + # bors the build completed, as there is no practical way to detect when a + # workflow is successful listening to webhooks only. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + end-success: + name: bors test finished + if: github.event.pusher.name == 'bors' && success() + runs-on: ubuntu-latest + needs: [base, integration] + + steps: + - name: Mark the job as successful + run: exit 0 + + end-failure: + name: bors test finished + if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + runs-on: ubuntu-latest + needs: [base, integration] + + steps: + - name: Mark the job as a failure + run: exit 1 diff --git a/src/tools/clippy/.github/workflows/clippy_dev.yml b/src/tools/clippy/.github/workflows/clippy_dev.yml new file mode 100644 index 0000000000000..ec3b43c2f43bc --- /dev/null +++ b/src/tools/clippy/.github/workflows/clippy_dev.yml @@ -0,0 +1,74 @@ +name: Clippy Dev Test + +on: + push: + branches: + - auto + - try + pull_request: + # Only run on paths, that get checked by the clippy_dev tool + paths: + - 'CHANGELOG.md' + - 'README.md' + - '**.stderr' + - '**.rs' + +env: + RUST_BACKTRACE: 1 + +jobs: + clippy_dev: + runs-on: ubuntu-latest + + steps: + # Setup + - name: rust-toolchain + uses: actions-rs/toolchain@v1.0.3 + with: + toolchain: nightly + target: x86_64-unknown-linux-gnu + profile: minimal + components: rustfmt + + - name: Checkout + uses: actions/checkout@v2.0.0 + + # Run + - name: Build + run: cargo build --features deny-warnings + working-directory: clippy_dev + + - name: Test limit_stderr_length + run: cargo dev limit_stderr_length + + - name: Test update_lints + run: cargo dev update_lints --check + + - name: Test fmt + run: cargo dev fmt --check + + # These jobs doesn't actually test anything, but they're only used to tell + # bors the build completed, as there is no practical way to detect when a + # workflow is successful listening to webhooks only. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + end-success: + name: bors dev test finished + if: github.event.pusher.name == 'bors' && success() + runs-on: ubuntu-latest + needs: [clippy_dev] + + steps: + - name: Mark the job as successful + run: exit 0 + + end-failure: + name: bors dev test finished + if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + runs-on: ubuntu-latest + needs: [clippy_dev] + + steps: + - name: Mark the job as a failure + run: exit 1 diff --git a/src/tools/clippy/.github/workflows/deploy.yml b/src/tools/clippy/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..f542f9b02c17b --- /dev/null +++ b/src/tools/clippy/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Deploy + +on: + push: + branches: + - master + - beta + tags: + - rust-1.** + +env: + TARGET_BRANCH: 'gh-pages' + SHA: '${{ github.sha }}' + SSH_REPO: 'git@github.com:${{ github.repository }}.git' + +jobs: + deploy: + runs-on: ubuntu-latest + if: github.repository == 'rust-lang/rust-clippy' + + steps: + # Setup + - name: Checkout + uses: actions/checkout@v2.0.0 + + - name: Checkout + uses: actions/checkout@v2.0.0 + with: + ref: ${{ env.TARGET_BRANCH }} + path: 'out' + + # Run + - name: Set tag name + if: startswith(github.ref, 'refs/tags/') + run: | + TAG=$(basename ${{ github.ref }}) + echo "::set-env name=TAG_NAME::$TAG" + - name: Set beta to true + if: github.ref == 'refs/heads/beta' + run: echo "::set-env name=BETA::true" + + - name: Use scripts and templates from master branch + run: | + git fetch --no-tags --prune --depth=1 origin master + git checkout origin/master -- .github/deploy.sh util/gh-pages/ util/*.py + + - name: Deploy + run: | + eval "$(ssh-agent -s)" + ssh-add - <<< "${{ secrets.DEPLOY_KEY }}" + bash .github/deploy.sh diff --git a/src/tools/clippy/.github/workflows/remark.yml b/src/tools/clippy/.github/workflows/remark.yml new file mode 100644 index 0000000000000..cc175e8bf247f --- /dev/null +++ b/src/tools/clippy/.github/workflows/remark.yml @@ -0,0 +1,55 @@ +name: Remark + +on: + push: + branches: + - auto + - try + pull_request: + paths: + - '**.md' + +jobs: + remark: + runs-on: ubuntu-latest + + steps: + # Setup + - name: Checkout + uses: actions/checkout@v2.0.0 + + - name: Setup Node.js + uses: actions/setup-node@v1.1.0 + + - name: Install remark + run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended + + # Run + - name: Check *.md files + run: git ls-files -z '*.md' | xargs -0 -n 1 -I {} ./node_modules/.bin/remark {} -u lint -f > /dev/null + + # These jobs doesn't actually test anything, but they're only used to tell + # bors the build completed, as there is no practical way to detect when a + # workflow is successful listening to webhooks only. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + end-success: + name: bors remark test finished + if: github.event.pusher.name == 'bors' && success() + runs-on: ubuntu-latest + needs: [remark] + + steps: + - name: Mark the job as successful + run: exit 0 + + end-failure: + name: bors remark test finished + if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + runs-on: ubuntu-latest + needs: [remark] + + steps: + - name: Mark the job as a failure + run: exit 1 diff --git a/src/tools/clippy/.gitignore b/src/tools/clippy/.gitignore new file mode 100644 index 0000000000000..adf5e8feddf4c --- /dev/null +++ b/src/tools/clippy/.gitignore @@ -0,0 +1,37 @@ +# Used by CI to be able to push: +/.github/deploy_key +out + +# Compiled files +*.o +*.d +*.so +*.rlib +*.dll +*.pyc +*.rmeta + +# Executables +*.exe + +# Generated by Cargo +*Cargo.lock +/target +/clippy_lints/target +/clippy_workspace_tests/target +/clippy_dev/target +/rustc_tools_util/target + +# Generated by dogfood +/target_recur/ + +# gh pages docs +util/gh-pages/lints.json + +# rustfmt backups +*.rs.bk + +helper.txt +*.iml +.vscode +.idea diff --git a/src/tools/clippy/.remarkrc b/src/tools/clippy/.remarkrc new file mode 100644 index 0000000000000..0ede7ac75cb6f --- /dev/null +++ b/src/tools/clippy/.remarkrc @@ -0,0 +1,12 @@ +{ + "plugins": [ + "remark-preset-lint-recommended", + ["remark-lint-list-item-indent", false], + ["remark-lint-no-literal-urls", false], + ["remark-lint-no-shortcut-reference-link", false], + ["remark-lint-maximum-line-length", 120] + ], + "settings": { + "commonmark": true + } +} diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md new file mode 100644 index 0000000000000..575773580c0be --- /dev/null +++ b/src/tools/clippy/CHANGELOG.md @@ -0,0 +1,1657 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Changelog Update](doc/changelog_update.md) if you want to update this +document. + +## Unreleased / In Rust Nightly + +[891e1a8...master](https://github.com/rust-lang/rust-clippy/compare/891e1a8...master) + +## Rust 1.44 + +Current beta, release 2020-06-04 + +[204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8) + +### New lints + +* [`explicit_deref_methods`] [#5226](https://github.com/rust-lang/rust-clippy/pull/5226) +* [`implicit_saturating_sub`] [#5427](https://github.com/rust-lang/rust-clippy/pull/5427) +* [`macro_use_imports`] [#5230](https://github.com/rust-lang/rust-clippy/pull/5230) +* [`verbose_file_reads`] [#5272](https://github.com/rust-lang/rust-clippy/pull/5272) +* [`future_not_send`] [#5423](https://github.com/rust-lang/rust-clippy/pull/5423) +* [`redundant_pub_crate`] [#5319](https://github.com/rust-lang/rust-clippy/pull/5319) +* [`large_const_arrays`] [#5248](https://github.com/rust-lang/rust-clippy/pull/5248) +* [`result_map_or_into_option`] [#5415](https://github.com/rust-lang/rust-clippy/pull/5415) +* [`redundant_allocation`] [#5349](https://github.com/rust-lang/rust-clippy/pull/5349) +* [`fn_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294) +* [`vtable_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294) + + +### Moves and Deprecations + +* Deprecate [`replace_consts`] lint [#5380](https://github.com/rust-lang/rust-clippy/pull/5380) +* Move [`cognitive_complexity`] to nursery [#5428](https://github.com/rust-lang/rust-clippy/pull/5428) +* Move [`useless_transmute`] to nursery [#5364](https://github.com/rust-lang/rust-clippy/pull/5364) +* Downgrade [`inefficient_to_string`] to pedantic [#5412](https://github.com/rust-lang/rust-clippy/pull/5412) +* Downgrade [`option_option`] to pedantic [#5401](https://github.com/rust-lang/rust-clippy/pull/5401) +* Downgrade [`unreadable_literal`] to pedantic [#5419](https://github.com/rust-lang/rust-clippy/pull/5419) +* Downgrade [`let_unit_value`] to pedantic [#5409](https://github.com/rust-lang/rust-clippy/pull/5409) +* Downgrade [`trivially_copy_pass_by_ref`] to pedantic [#5410](https://github.com/rust-lang/rust-clippy/pull/5410) +* Downgrade [`implicit_hasher`] to pedantic [#5411](https://github.com/rust-lang/rust-clippy/pull/5411) + +### Enhancements + +* On _nightly_ you can now use `cargo clippy --fix -Z unstable-options` to + auto-fix lints that support this [#5363](https://github.com/rust-lang/rust-clippy/pull/5363) +* Make [`redundant_clone`] also trigger on cases where the cloned value is not + consumed. [#5304](https://github.com/rust-lang/rust-clippy/pull/5304) +* Expand [`integer_arithmetic`] to also disallow bit-shifting [#5430](https://github.com/rust-lang/rust-clippy/pull/5430) +* [`option_as_ref_deref`] now detects more deref cases [#5425](https://github.com/rust-lang/rust-clippy/pull/5425) +* [`large_enum_variant`] now report the sizes of the largest and second-largest variants [#5466](https://github.com/rust-lang/rust-clippy/pull/5466) +* [`bool_comparison`] now also checks for inequality comparisons that can be + written more concisely [#5365](https://github.com/rust-lang/rust-clippy/pull/5365) +* Expand [`clone_on_copy`] to work in method call arguments as well [#5441](https://github.com/rust-lang/rust-clippy/pull/5441) +* [`redundant_pattern_matching`] now also handles `while let` [#5483](https://github.com/rust-lang/rust-clippy/pull/5483) +* [`integer_arithmetic`] now also lints references of integers [#5329](https://github.com/rust-lang/rust-clippy/pull/5329) +* Expand [`float_cmp_const`] to also work on arrays [#5345](https://github.com/rust-lang/rust-clippy/pull/5345) +* Trigger [`map_flatten`] when map is called on an `Option` [#5473](https://github.com/rust-lang/rust-clippy/pull/5473) + +### False Positive Fixes + +* [`many_single_char_names`] [#5468](https://github.com/rust-lang/rust-clippy/pull/5468) +* [`should_implement_trait`] [#5437](https://github.com/rust-lang/rust-clippy/pull/5437) +* [`unused_self`] [#5387](https://github.com/rust-lang/rust-clippy/pull/5387) +* [`redundant_clone`] [#5453](https://github.com/rust-lang/rust-clippy/pull/5453) +* [`precedence`] [#5445](https://github.com/rust-lang/rust-clippy/pull/5445) +* [`suspicious_op_assign_impl`] [#5424](https://github.com/rust-lang/rust-clippy/pull/5424) +* [`needless_lifetimes`] [#5293](https://github.com/rust-lang/rust-clippy/pull/5293) +* [`redundant_pattern`] [#5287](https://github.com/rust-lang/rust-clippy/pull/5287) +* [`inconsistent_digit_grouping`] [#5451](https://github.com/rust-lang/rust-clippy/pull/5451) + + +### Suggestion Improvements + +* Improved [`question_mark`] lint suggestion so that it doesn't add redundant `as_ref()` [#5481](https://github.com/rust-lang/rust-clippy/pull/5481) +* Improve the suggested placeholder in [`option_map_unit_fn`] [#5292](https://github.com/rust-lang/rust-clippy/pull/5292) +* Improve suggestion for [`match_single_binding`] when triggered inside a closure [#5350](https://github.com/rust-lang/rust-clippy/pull/5350) + +### ICE Fixes + +* Handle the unstable `trivial_bounds` feature [#5296](https://github.com/rust-lang/rust-clippy/pull/5296) +* `shadow_*` lints [#5297](https://github.com/rust-lang/rust-clippy/pull/5297) + +### Documentation + +* Fix documentation generation for configurable lints [#5353](https://github.com/rust-lang/rust-clippy/pull/5353) +* Update documentation for [`new_ret_no_self`] [#5448](https://github.com/rust-lang/rust-clippy/pull/5448) +* The documentation for [`option_option`] now suggest using a tri-state enum [#5403](https://github.com/rust-lang/rust-clippy/pull/5403) +* Fix bit mask example in [`verbose_bit_mask`] documentation [#5454](https://github.com/rust-lang/rust-clippy/pull/5454) +* [`wildcard_imports`] documentation now mentions that `use ...::prelude::*` is + not linted [#5312](https://github.com/rust-lang/rust-clippy/pull/5312) + +## Rust 1.43 + +Current stable, released 2020-04-23 + +[4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b) + +### New lints + +* [`imprecise_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897) +* [`suboptimal_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897) +* [`wildcard_imports`] [#5029](https://github.com/rust-lang/rust-clippy/pull/5029) +* [`single_component_path_imports`] [#5058](https://github.com/rust-lang/rust-clippy/pull/5058) +* [`match_single_binding`] [#5061](https://github.com/rust-lang/rust-clippy/pull/5061) +* [`let_underscore_lock`] [#5101](https://github.com/rust-lang/rust-clippy/pull/5101) +* [`struct_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125) +* [`fn_params_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125) +* [`option_env_unwrap`] [#5148](https://github.com/rust-lang/rust-clippy/pull/5148) +* [`lossy_float_literal`] [#5202](https://github.com/rust-lang/rust-clippy/pull/5202) +* [`rest_pat_in_fully_bound_structs`] [#5258](https://github.com/rust-lang/rust-clippy/pull/5258) + +### Moves and Deprecations + +* Move [`unneeded_field_pattern`] to pedantic group [#5200](https://github.com/rust-lang/rust-clippy/pull/5200) + +### Enhancements + +* Make [`missing_errors_doc`] lint also trigger on `async` functions + [#5181](https://github.com/rust-lang/rust-clippy/pull/5181) +* Add more constants to [`approx_constant`] [#5193](https://github.com/rust-lang/rust-clippy/pull/5193) +* Extend [`question_mark`] lint [#5266](https://github.com/rust-lang/rust-clippy/pull/5266) + +### False Positive Fixes + +* [`use_debug`] [#5047](https://github.com/rust-lang/rust-clippy/pull/5047) +* [`unnecessary_unwrap`] [#5132](https://github.com/rust-lang/rust-clippy/pull/5132) +* [`zero_prefixed_literal`] [#5170](https://github.com/rust-lang/rust-clippy/pull/5170) +* [`missing_const_for_fn`] [#5216](https://github.com/rust-lang/rust-clippy/pull/5216) + +### Suggestion Improvements + +* Improve suggestion when blocks of code are suggested [#5134](https://github.com/rust-lang/rust-clippy/pull/5134) + +### ICE Fixes + +* `misc_early` lints [#5129](https://github.com/rust-lang/rust-clippy/pull/5129) +* [`missing_errors_doc`] [#5213](https://github.com/rust-lang/rust-clippy/pull/5213) +* Fix ICE when evaluating `usize`s [#5256](https://github.com/rust-lang/rust-clippy/pull/5256) + +### Documentation + +* Improve documentation of [`iter_nth_zero`] +* Add documentation pages for stable releases [#5171](https://github.com/rust-lang/rust-clippy/pull/5171) + +### Others + +* Clippy now completely runs on GitHub Actions [#5190](https://github.com/rust-lang/rust-clippy/pull/5190) + + +## Rust 1.42 + +Released 2020-03-12 + +[69f99e7...4ee1206](https://github.com/rust-lang/rust-clippy/compare/69f99e7...4ee1206) + +### New lints + +* [`filetype_is_file`] [#4543](https://github.com/rust-lang/rust-clippy/pull/4543) +* [`let_underscore_must_use`] [#4823](https://github.com/rust-lang/rust-clippy/pull/4823) +* [`modulo_arithmetic`] [#4867](https://github.com/rust-lang/rust-clippy/pull/4867) +* [`mem_replace_with_default`] [#4881](https://github.com/rust-lang/rust-clippy/pull/4881) +* [`mutable_key_type`] [#4885](https://github.com/rust-lang/rust-clippy/pull/4885) +* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945) +* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960) +* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966) +* [`invalid_atomic_ordering`] [#4999](https://github.com/rust-lang/rust-clippy/pull/4999) +* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067) + +### Moves and Deprecations + +* Move [`transmute_float_to_int`] from nursery to complexity group + [#5015](https://github.com/rust-lang/rust-clippy/pull/5015) +* Move [`range_plus_one`] to pedantic group [#5057](https://github.com/rust-lang/rust-clippy/pull/5057) +* Move [`debug_assert_with_mut_call`] to nursery group [#5106](https://github.com/rust-lang/rust-clippy/pull/5106) +* Deprecate [`unused_label`] [#4930](https://github.com/rust-lang/rust-clippy/pull/4930) + +### Enhancements + +* Lint vectored IO in [`unused_io_amount`] [#5027](https://github.com/rust-lang/rust-clippy/pull/5027) +* Make [`vec_box`] configurable by adding a size threshold [#5081](https://github.com/rust-lang/rust-clippy/pull/5081) +* Also lint constants in [`cmp_nan`] [#4910](https://github.com/rust-lang/rust-clippy/pull/4910) +* Fix false negative in [`expect_fun_call`] [#4915](https://github.com/rust-lang/rust-clippy/pull/4915) +* Fix false negative in [`redundant_clone`] [#5017](https://github.com/rust-lang/rust-clippy/pull/5017) + +### False Positive Fixes + +* [`map_clone`] [#4937](https://github.com/rust-lang/rust-clippy/pull/4937) +* [`replace_consts`] [#4977](https://github.com/rust-lang/rust-clippy/pull/4977) +* [`let_and_return`] [#5008](https://github.com/rust-lang/rust-clippy/pull/5008) +* [`eq_op`] [#5079](https://github.com/rust-lang/rust-clippy/pull/5079) +* [`possible_missing_comma`] [#5083](https://github.com/rust-lang/rust-clippy/pull/5083) +* [`debug_assert_with_mut_call`] [#5106](https://github.com/rust-lang/rust-clippy/pull/5106) +* Don't trigger [`let_underscore_must_use`] in external macros + [#5082](https://github.com/rust-lang/rust-clippy/pull/5082) +* Don't trigger [`empty_loop`] in `no_std` crates [#5086](https://github.com/rust-lang/rust-clippy/pull/5086) + +### Suggestion Improvements + +* [`option_map_unwrap_or`] [#4634](https://github.com/rust-lang/rust-clippy/pull/4634) +* [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934) +* [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935) +* [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956) +* [`unknown_clippy_lints`] [#4963](https://github.com/rust-lang/rust-clippy/pull/4963) +* [`explicit_into_iter_loop`] [#4978](https://github.com/rust-lang/rust-clippy/pull/4978) +* [`useless_attribute`] [#5022](https://github.com/rust-lang/rust-clippy/pull/5022) +* [`if_let_some_result`] [#5032](https://github.com/rust-lang/rust-clippy/pull/5032) + +### ICE fixes + +* [`unsound_collection_transmute`] [#4975](https://github.com/rust-lang/rust-clippy/pull/4975) + +### Documentation + +* Improve documentation of [`empty_enum`], [`replace_consts`], [`redundant_clone`], and [`iterator_step_by_zero`] + + +## Rust 1.41 + +Released 2020-01-30 + +[c8e3cfb...69f99e7](https://github.com/rust-lang/rust-clippy/compare/c8e3cfb...69f99e7) + +* New Lints: + * [`exit`] [#4697](https://github.com/rust-lang/rust-clippy/pull/4697) + * [`to_digit_is_some`] [#4801](https://github.com/rust-lang/rust-clippy/pull/4801) + * [`tabs_in_doc_comments`] [#4806](https://github.com/rust-lang/rust-clippy/pull/4806) + * [`large_stack_arrays`] [#4807](https://github.com/rust-lang/rust-clippy/pull/4807) + * [`same_functions_in_if_condition`] [#4814](https://github.com/rust-lang/rust-clippy/pull/4814) + * [`zst_offset`] [#4816](https://github.com/rust-lang/rust-clippy/pull/4816) + * [`as_conversions`] [#4821](https://github.com/rust-lang/rust-clippy/pull/4821) + * [`missing_errors_doc`] [#4884](https://github.com/rust-lang/rust-clippy/pull/4884) + * [`transmute_float_to_int`] [#4889](https://github.com/rust-lang/rust-clippy/pull/4889) +* Remove plugin interface, see + [Inside Rust Blog](https://blog.rust-lang.org/inside-rust/2019/11/04/Clippy-removes-plugin-interface.html) for + details [#4714](https://github.com/rust-lang/rust-clippy/pull/4714) +* Move [`use_self`] to nursery group [#4863](https://github.com/rust-lang/rust-clippy/pull/4863) +* Deprecate [`into_iter_on_array`] [#4788](https://github.com/rust-lang/rust-clippy/pull/4788) +* Expand [`string_lit_as_bytes`] to also trigger when literal has escapes + [#4808](https://github.com/rust-lang/rust-clippy/pull/4808) +* Fix false positive in `comparison_chain` [#4842](https://github.com/rust-lang/rust-clippy/pull/4842) +* Fix false positive in `while_immutable_condition` [#4730](https://github.com/rust-lang/rust-clippy/pull/4730) +* Fix false positive in `explicit_counter_loop` [#4803](https://github.com/rust-lang/rust-clippy/pull/4803) +* Fix false positive in `must_use_candidate` [#4794](https://github.com/rust-lang/rust-clippy/pull/4794) +* Fix false positive in `print_with_newline` and `write_with_newline` + [#4769](https://github.com/rust-lang/rust-clippy/pull/4769) +* Fix false positive in `derive_hash_xor_eq` [#4766](https://github.com/rust-lang/rust-clippy/pull/4766) +* Fix false positive in `missing_inline_in_public_items` [#4870](https://github.com/rust-lang/rust-clippy/pull/4870) +* Fix false positive in `string_add` [#4880](https://github.com/rust-lang/rust-clippy/pull/4880) +* Fix false positive in `float_arithmetic` [#4851](https://github.com/rust-lang/rust-clippy/pull/4851) +* Fix false positive in `cast_sign_loss` [#4883](https://github.com/rust-lang/rust-clippy/pull/4883) +* Fix false positive in `manual_swap` [#4877](https://github.com/rust-lang/rust-clippy/pull/4877) +* Fix ICEs occurring while checking some block expressions [#4772](https://github.com/rust-lang/rust-clippy/pull/4772) +* Fix ICE in `use_self` [#4776](https://github.com/rust-lang/rust-clippy/pull/4776) +* Fix ICEs related to `const_generics` [#4780](https://github.com/rust-lang/rust-clippy/pull/4780) +* Display help when running `clippy-driver` without arguments, instead of ICEing + [#4810](https://github.com/rust-lang/rust-clippy/pull/4810) +* Clippy has its own ICE message now [#4588](https://github.com/rust-lang/rust-clippy/pull/4588) +* Show deprecated lints in the documentation again [#4757](https://github.com/rust-lang/rust-clippy/pull/4757) +* Improve Documentation by adding positive examples to some lints + [#4832](https://github.com/rust-lang/rust-clippy/pull/4832) + +## Rust 1.40 + +Released 2019-12-19 + +[4e7e71b...c8e3cfb](https://github.com/rust-lang/rust-clippy/compare/4e7e71b...c8e3cfb) + +* New Lints: + * [`unneeded_wildcard_pattern`] [#4537](https://github.com/rust-lang/rust-clippy/pull/4537) + * [`needless_doctest_main`] [#4603](https://github.com/rust-lang/rust-clippy/pull/4603) + * [`suspicious_unary_op_formatting`] [#4615](https://github.com/rust-lang/rust-clippy/pull/4615) + * [`debug_assert_with_mut_call`] [#4680](https://github.com/rust-lang/rust-clippy/pull/4680) + * [`unused_self`] [#4619](https://github.com/rust-lang/rust-clippy/pull/4619) + * [`inefficient_to_string`] [#4683](https://github.com/rust-lang/rust-clippy/pull/4683) + * [`must_use_unit`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560) + * [`must_use_candidate`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560) + * [`double_must_use`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560) + * [`comparison_chain`] [#4569](https://github.com/rust-lang/rust-clippy/pull/4569) + * [`unsound_collection_transmute`] [#4592](https://github.com/rust-lang/rust-clippy/pull/4592) + * [`panic`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * [`unreachable`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * [`todo`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * [`option_expect_used`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) + * [`result_expect_used`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657) +* Move `redundant_clone` to perf group [#4509](https://github.com/rust-lang/rust-clippy/pull/4509) +* Move `manual_mul_add` to nursery group [#4736](https://github.com/rust-lang/rust-clippy/pull/4736) +* Expand `unit_cmp` to also work with `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` [#4613](https://github.com/rust-lang/rust-clippy/pull/4613) +* Expand `integer_arithmetic` to also detect mutating arithmetic like `+=` [#4585](https://github.com/rust-lang/rust-clippy/pull/4585) +* Fix false positive in `nonminimal_bool` [#4568](https://github.com/rust-lang/rust-clippy/pull/4568) +* Fix false positive in `missing_safety_doc` [#4611](https://github.com/rust-lang/rust-clippy/pull/4611) +* Fix false positive in `cast_sign_loss` [#4614](https://github.com/rust-lang/rust-clippy/pull/4614) +* Fix false positive in `redundant_clone` [#4509](https://github.com/rust-lang/rust-clippy/pull/4509) +* Fix false positive in `try_err` [#4721](https://github.com/rust-lang/rust-clippy/pull/4721) +* Fix false positive in `toplevel_ref_arg` [#4570](https://github.com/rust-lang/rust-clippy/pull/4570) +* Fix false positive in `multiple_inherent_impl` [#4593](https://github.com/rust-lang/rust-clippy/pull/4593) +* Improve more suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4575](https://github.com/rust-lang/rust-clippy/pull/4575) +* Improve suggestion for `zero_ptr` [#4599](https://github.com/rust-lang/rust-clippy/pull/4599) +* Improve suggestion for `explicit_counter_loop` [#4691](https://github.com/rust-lang/rust-clippy/pull/4691) +* Improve suggestion for `mul_add` [#4602](https://github.com/rust-lang/rust-clippy/pull/4602) +* Improve suggestion for `assertions_on_constants` [#4635](https://github.com/rust-lang/rust-clippy/pull/4635) +* Fix ICE in `use_self` [#4671](https://github.com/rust-lang/rust-clippy/pull/4671) +* Fix ICE when encountering const casts [#4590](https://github.com/rust-lang/rust-clippy/pull/4590) + +## Rust 1.39 + +Released 2019-11-07 + +[3aea860...4e7e71b](https://github.com/rust-lang/rust-clippy/compare/3aea860...4e7e71b) + +* New Lints: + * [`uninit_assumed_init`] [#4479](https://github.com/rust-lang/rust-clippy/pull/4479) + * [`flat_map_identity`] [#4231](https://github.com/rust-lang/rust-clippy/pull/4231) + * [`missing_safety_doc`] [#4535](https://github.com/rust-lang/rust-clippy/pull/4535) + * [`mem_replace_with_uninit`] [#4511](https://github.com/rust-lang/rust-clippy/pull/4511) + * [`suspicious_map`] [#4394](https://github.com/rust-lang/rust-clippy/pull/4394) + * [`option_and_then_some`] [#4386](https://github.com/rust-lang/rust-clippy/pull/4386) + * [`manual_saturating_arithmetic`] [#4498](https://github.com/rust-lang/rust-clippy/pull/4498) +* Deprecate `unused_collect` lint. This is fully covered by rustc's `#[must_use]` on `collect` [#4348](https://github.com/rust-lang/rust-clippy/pull/4348) +* Move `type_repetition_in_bounds` to pedantic group [#4403](https://github.com/rust-lang/rust-clippy/pull/4403) +* Move `cast_lossless` to pedantic group [#4539](https://github.com/rust-lang/rust-clippy/pull/4539) +* `temporary_cstring_as_ptr` now catches more cases [#4425](https://github.com/rust-lang/rust-clippy/pull/4425) +* `use_self` now works in constructors, too [#4525](https://github.com/rust-lang/rust-clippy/pull/4525) +* `cargo_common_metadata` now checks for license files [#4518](https://github.com/rust-lang/rust-clippy/pull/4518) +* `cognitive_complexity` now includes the measured complexity in the warning message [#4469](https://github.com/rust-lang/rust-clippy/pull/4469) +* Fix false positives in `block_in_if_*` lints [#4458](https://github.com/rust-lang/rust-clippy/pull/4458) +* Fix false positive in `cast_lossless` [#4473](https://github.com/rust-lang/rust-clippy/pull/4473) +* Fix false positive in `clone_on_copy` [#4411](https://github.com/rust-lang/rust-clippy/pull/4411) +* Fix false positive in `deref_addrof` [#4487](https://github.com/rust-lang/rust-clippy/pull/4487) +* Fix false positive in `too_many_lines` [#4490](https://github.com/rust-lang/rust-clippy/pull/4490) +* Fix false positive in `new_ret_no_self` [#4365](https://github.com/rust-lang/rust-clippy/pull/4365) +* Fix false positive in `manual_swap` [#4478](https://github.com/rust-lang/rust-clippy/pull/4478) +* Fix false positive in `missing_const_for_fn` [#4450](https://github.com/rust-lang/rust-clippy/pull/4450) +* Fix false positive in `extra_unused_lifetimes` [#4477](https://github.com/rust-lang/rust-clippy/pull/4477) +* Fix false positive in `inherent_to_string` [#4460](https://github.com/rust-lang/rust-clippy/pull/4460) +* Fix false positive in `map_entry` [#4495](https://github.com/rust-lang/rust-clippy/pull/4495) +* Fix false positive in `unused_unit` [#4445](https://github.com/rust-lang/rust-clippy/pull/4445) +* Fix false positive in `redundant_pattern` [#4489](https://github.com/rust-lang/rust-clippy/pull/4489) +* Fix false positive in `wrong_self_convention` [#4369](https://github.com/rust-lang/rust-clippy/pull/4369) +* Improve various suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4558](https://github.com/rust-lang/rust-clippy/pull/4558) +* Improve suggestions for `redundant_pattern_matching` [#4352](https://github.com/rust-lang/rust-clippy/pull/4352) +* Improve suggestions for `explicit_write` [#4544](https://github.com/rust-lang/rust-clippy/pull/4544) +* Improve suggestion for `or_fun_call` [#4522](https://github.com/rust-lang/rust-clippy/pull/4522) +* Improve suggestion for `match_as_ref` [#4446](https://github.com/rust-lang/rust-clippy/pull/4446) +* Improve suggestion for `unnecessary_fold_span` [#4382](https://github.com/rust-lang/rust-clippy/pull/4382) +* Add suggestions for `unseparated_literal_suffix` [#4401](https://github.com/rust-lang/rust-clippy/pull/4401) +* Add suggestions for `char_lit_as_u8` [#4418](https://github.com/rust-lang/rust-clippy/pull/4418) + +## Rust 1.38 + +Released 2019-09-26 + +[e3cb40e...3aea860](https://github.com/rust-lang/rust-clippy/compare/e3cb40e...3aea860) + +* New Lints: + * [`main_recursion`] [#4203](https://github.com/rust-lang/rust-clippy/pull/4203) + * [`inherent_to_string`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259) + * [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259) + * [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766) + * [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222) +* Move `{unnnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307) +* Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308) +* Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262) +* Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337) +* Fix false positive in `pub_enum_variant_names` and `enum_variant_names` [#4345](https://github.com/rust-lang/rust-clippy/pull/4345) +* Fix false positive in `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257) +* Fix false positive in `string_lit_as_bytes` [#4233](https://github.com/rust-lang/rust-clippy/pull/4233) +* Fix false positive in `needless_lifetimes` [#4266](https://github.com/rust-lang/rust-clippy/pull/4266) +* Fix false positive in `float_cmp` [#4275](https://github.com/rust-lang/rust-clippy/pull/4275) +* Fix false positives in `needless_return` [#4274](https://github.com/rust-lang/rust-clippy/pull/4274) +* Fix false negative in `match_same_arms` [#4246](https://github.com/rust-lang/rust-clippy/pull/4246) +* Fix incorrect suggestion for `needless_bool` [#4335](https://github.com/rust-lang/rust-clippy/pull/4335) +* Improve suggestion for `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257) +* Improve suggestion for `single_char_literal` [#4361](https://github.com/rust-lang/rust-clippy/pull/4361) +* Improve suggestion for `len_zero` [#4314](https://github.com/rust-lang/rust-clippy/pull/4314) +* Fix ICE in `implicit_hasher` [#4268](https://github.com/rust-lang/rust-clippy/pull/4268) +* Fix allow bug in `trivially_copy_pass_by_ref` [#4250](https://github.com/rust-lang/rust-clippy/pull/4250) + +## Rust 1.37 + +Released 2019-08-15 + +[082cfa7...e3cb40e](https://github.com/rust-lang/rust-clippy/compare/082cfa7...e3cb40e) + +* New Lints: + * [`checked_conversions`] [#4088](https://github.com/rust-lang/rust-clippy/pull/4088) + * [`get_last_with_len`] [#3832](https://github.com/rust-lang/rust-clippy/pull/3832) + * [`integer_division`] [#4195](https://github.com/rust-lang/rust-clippy/pull/4195) +* Renamed Lint: `const_static_lifetime` is now called [`redundant_static_lifetimes`]. + The lint now covers statics in addition to consts [#4162](https://github.com/rust-lang/rust-clippy/pull/4162) +* [`match_same_arms`] now warns for all identical arms, instead of only the first one [#4102](https://github.com/rust-lang/rust-clippy/pull/4102) +* [`needless_return`] now works with void functions [#4220](https://github.com/rust-lang/rust-clippy/pull/4220) +* Fix false positive in [`redundant_closure`] [#4190](https://github.com/rust-lang/rust-clippy/pull/4190) +* Fix false positive in [`useless_attribute`] [#4107](https://github.com/rust-lang/rust-clippy/pull/4107) +* Fix incorrect suggestion for [`float_cmp`] [#4214](https://github.com/rust-lang/rust-clippy/pull/4214) +* Add suggestions for [`print_with_newline`] and [`write_with_newline`] [#4136](https://github.com/rust-lang/rust-clippy/pull/4136) +* Improve suggestions for [`option_map_unwrap_or_else`] and [`result_map_unwrap_or_else`] [#4164](https://github.com/rust-lang/rust-clippy/pull/4164) +* Improve suggestions for [`non_ascii_literal`] [#4119](https://github.com/rust-lang/rust-clippy/pull/4119) +* Improve diagnostics for [`let_and_return`] [#4137](https://github.com/rust-lang/rust-clippy/pull/4137) +* Improve diagnostics for [`trivially_copy_pass_by_ref`] [#4071](https://github.com/rust-lang/rust-clippy/pull/4071) +* Add macro check for [`unreadable_literal`] [#4099](https://github.com/rust-lang/rust-clippy/pull/4099) + +## Rust 1.36 + +Released 2019-07-04 + +[eb9f9b1...082cfa7](https://github.com/rust-lang/rust-clippy/compare/eb9f9b1...082cfa7) + +* New lints: [`find_map`], [`filter_map_next`] [#4039](https://github.com/rust-lang/rust-clippy/pull/4039) +* New lint: [`path_buf_push_overwrite`] [#3954](https://github.com/rust-lang/rust-clippy/pull/3954) +* Move `path_buf_push_overwrite` to the nursery [#4013](https://github.com/rust-lang/rust-clippy/pull/4013) +* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101) +* Allow allowing of [`toplevel_ref_arg`] lint [#4007](https://github.com/rust-lang/rust-clippy/pull/4007) +* Fix false negative in [`or_fun_call`] pertaining to nested constructors [#4084](https://github.com/rust-lang/rust-clippy/pull/4084) +* Fix false positive in [`or_fun_call`] pertaining to enum variant constructors [#4018](https://github.com/rust-lang/rust-clippy/pull/4018) +* Fix false positive in [`useless_let_if_seq`] pertaining to interior mutability [#4035](https://github.com/rust-lang/rust-clippy/pull/4035) +* Fix false positive in [`redundant_closure`] pertaining to non-function types [#4008](https://github.com/rust-lang/rust-clippy/pull/4008) +* Fix false positive in [`let_and_return`] pertaining to attributes on `let`s [#4024](https://github.com/rust-lang/rust-clippy/pull/4024) +* Fix false positive in [`module_name_repetitions`] lint pertaining to attributes [#4006](https://github.com/rust-lang/rust-clippy/pull/4006) +* Fix false positive on [`assertions_on_constants`] pertaining to `debug_assert!` [#3989](https://github.com/rust-lang/rust-clippy/pull/3989) +* Improve suggestion in [`map_clone`] to suggest `.copied()` where applicable [#3970](https://github.com/rust-lang/rust-clippy/pull/3970) [#4043](https://github.com/rust-lang/rust-clippy/pull/4043) +* Improve suggestion for [`search_is_some`] [#4049](https://github.com/rust-lang/rust-clippy/pull/4049) +* Improve suggestion applicability for [`naive_bytecount`] [#3984](https://github.com/rust-lang/rust-clippy/pull/3984) +* Improve suggestion applicability for [`while_let_loop`] [#3975](https://github.com/rust-lang/rust-clippy/pull/3975) +* Improve diagnostics for [`too_many_arguments`] [#4053](https://github.com/rust-lang/rust-clippy/pull/4053) +* Improve diagnostics for [`cast_lossless`] [#4021](https://github.com/rust-lang/rust-clippy/pull/4021) +* Deal with macro checks in desugarings better [#4082](https://github.com/rust-lang/rust-clippy/pull/4082) +* Add macro check for [`unnecessary_cast`] [#4026](https://github.com/rust-lang/rust-clippy/pull/4026) +* Remove [`approx_constant`]'s documentation's "Known problems" section. [#4027](https://github.com/rust-lang/rust-clippy/pull/4027) +* Fix ICE in [`suspicious_else_formatting`] [#3960](https://github.com/rust-lang/rust-clippy/pull/3960) +* Fix ICE in [`decimal_literal_representation`] [#3931](https://github.com/rust-lang/rust-clippy/pull/3931) + + +## Rust 1.35 + +Released 2019-05-20 + +[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e) + +* New lint: [`drop_bounds`] to detect `T: Drop` bounds +* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101) +* Rename `cyclomatic_complexity` to [`cognitive_complexity`], start work on making lint more practical for Rust code +* Move [`get_unwrap`] to the restriction category +* Improve suggestions for [`iter_cloned_collect`] +* Improve suggestions for [`cast_lossless`] to suggest suffixed literals +* Fix false positives in [`print_with_newline`] and [`write_with_newline`] pertaining to raw strings +* Fix false positive in [`needless_range_loop`] pertaining to structs without a `.iter()` +* Fix false positive in [`bool_comparison`] pertaining to non-bool types +* Fix false positive in [`redundant_closure`] pertaining to differences in borrows +* Fix false positive in [`option_map_unwrap_or`] on non-copy types +* Fix false positives in [`missing_const_for_fn`] pertaining to macros and trait method impls +* Fix false positive in [`needless_pass_by_value`] pertaining to procedural macros +* Fix false positive in [`needless_continue`] pertaining to loop labels +* Fix false positive for [`boxed_local`] pertaining to arguments moved into closures +* Fix false positive for [`use_self`] in nested functions +* Fix suggestion for [`expect_fun_call`] (https://github.com/rust-lang/rust-clippy/pull/3846) +* Fix suggestion for [`explicit_counter_loop`] to deal with parenthesizing range variables +* Fix suggestion for [`single_char_pattern`] to correctly escape single quotes +* Avoid triggering [`redundant_closure`] in macros +* ICE fixes: [#3805](https://github.com/rust-lang/rust-clippy/pull/3805), [#3772](https://github.com/rust-lang/rust-clippy/pull/3772), [#3741](https://github.com/rust-lang/rust-clippy/pull/3741) + +## Rust 1.34 + +Released 2019-04-10 + +[1b89724...1fac380](https://github.com/rust-lang/rust-clippy/compare/1b89724...1fac380) + +* New lint: [`assertions_on_constants`] to detect for example `assert!(true)` +* New lint: [`dbg_macro`] to detect uses of the `dbg!` macro +* New lint: [`missing_const_for_fn`] that can suggest functions to be made `const` +* New lint: [`too_many_lines`] to detect functions with excessive LOC. It can be + configured using the `too-many-lines-threshold` configuration. +* New lint: [`wildcard_enum_match_arm`] to check for wildcard enum matches using `_` +* Expand `redundant_closure` to also work for methods (not only functions) +* Fix ICEs in `vec_box`, `needless_pass_by_value` and `implicit_hasher` +* Fix false positive in `cast_sign_loss` +* Fix false positive in `integer_arithmetic` +* Fix false positive in `unit_arg` +* Fix false positives in `implicit_return` +* Add suggestion to `explicit_write` +* Improve suggestions for `question_mark` lint +* Fix incorrect suggestion for `cast_lossless` +* Fix incorrect suggestion for `expect_fun_call` +* Fix incorrect suggestion for `needless_bool` +* Fix incorrect suggestion for `needless_range_loop` +* Fix incorrect suggestion for `use_self` +* Fix incorrect suggestion for `while_let_on_iterator` +* Clippy is now slightly easier to invoke in non-cargo contexts. See + [#3665][pull3665] for more details. +* We now have [improved documentation][adding_lints] on how to add new lints + +## Rust 1.33 + +Released 2019-02-26 + +[b2601be...1b89724](https://github.com/rust-lang/rust-clippy/compare/b2601be...1b89724) + +* New lints: [`implicit_return`], [`vec_box`], [`cast_ref_to_mut`] +* The `rust-clippy` repository is now part of the `rust-lang` org. +* Rename `stutter` to `module_name_repetitions` +* Merge `new_without_default_derive` into `new_without_default` lint +* Move `large_digit_groups` from `style` group to `pedantic` +* Expand `bool_comparison` to check for `<`, `<=`, `>`, `>=`, and `!=` + comparisons against booleans +* Expand `no_effect` to detect writes to constants such as `A_CONST.field = 2` +* Expand `redundant_clone` to work on struct fields +* Expand `suspicious_else_formatting` to detect `if .. {..} {..}` +* Expand `use_self` to work on tuple structs and also in local macros +* Fix ICE in `result_map_unit_fn` and `option_map_unit_fn` +* Fix false positives in `implicit_return` +* Fix false positives in `use_self` +* Fix false negative in `clone_on_copy` +* Fix false positive in `doc_markdown` +* Fix false positive in `empty_loop` +* Fix false positive in `if_same_then_else` +* Fix false positive in `infinite_iter` +* Fix false positive in `question_mark` +* Fix false positive in `useless_asref` +* Fix false positive in `wildcard_dependencies` +* Fix false positive in `write_with_newline` +* Add suggestion to `explicit_write` +* Improve suggestions for `question_mark` lint +* Fix incorrect suggestion for `get_unwrap` + +## Rust 1.32 + +Released 2019-01-17 + +[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be) + +* New lints: [`slow_vector_initialization`], [`mem_discriminant_non_enum`], + [`redundant_clone`], [`wildcard_dependencies`], + [`into_iter_on_ref`], [`into_iter_on_array`], [`deprecated_cfg_attr`], + [`mem_discriminant_non_enum`], [`cargo_common_metadata`] +* Add support for `u128` and `i128` to integer related lints +* Add float support to `mistyped_literal_suffixes` +* Fix false positives in `use_self` +* Fix false positives in `missing_comma` +* Fix false positives in `new_ret_no_self` +* Fix false positives in `possible_missing_comma` +* Fix false positive in `integer_arithmetic` in constant items +* Fix false positive in `needless_borrow` +* Fix false positive in `out_of_bounds_indexing` +* Fix false positive in `new_without_default_derive` +* Fix false positive in `string_lit_as_bytes` +* Fix false negative in `out_of_bounds_indexing` +* Fix false negative in `use_self`. It will now also check existential types +* Fix incorrect suggestion for `redundant_closure_call` +* Fix various suggestions that contained expanded macros +* Fix `bool_comparison` triggering 3 times on on on the same code +* Expand `trivially_copy_pass_by_ref` to work on trait methods +* Improve suggestion for `needless_range_loop` +* Move `needless_pass_by_value` from `pedantic` group to `style` + +## Rust 1.31 + +Released 2018-12-06 + +[125907ad..2e26fdc2](https://github.com/rust-lang/rust-clippy/compare/125907ad..2e26fdc2) + +* Clippy has been relicensed under a dual MIT / Apache license. + See [#3093](https://github.com/rust-lang/rust-clippy/issues/3093) for more + information. +* With Rust 1.31, Clippy is no longer available via crates.io. The recommended + installation method is via `rustup component add clippy`. +* New lints: [`redundant_pattern_matching`], [`unnecessary_filter_map`], + [`unused_unit`], [`map_flatten`], [`mem_replace_option_with_none`] +* Fix ICE in `if_let_redundant_pattern_matching` +* Fix ICE in `needless_pass_by_value` when encountering a generic function + argument with a lifetime parameter +* Fix ICE in `needless_range_loop` +* Fix ICE in `single_char_pattern` when encountering a constant value +* Fix false positive in `assign_op_pattern` +* Fix false positive in `boxed_local` on trait implementations +* Fix false positive in `cmp_owned` +* Fix false positive in `collapsible_if` when conditionals have comments +* Fix false positive in `double_parens` +* Fix false positive in `excessive_precision` +* Fix false positive in `explicit_counter_loop` +* Fix false positive in `fn_to_numeric_cast_with_truncation` +* Fix false positive in `map_clone` +* Fix false positive in `new_ret_no_self` +* Fix false positive in `new_without_default` when `new` is unsafe +* Fix false positive in `type_complexity` when using extern types +* Fix false positive in `useless_format` +* Fix false positive in `wrong_self_convention` +* Fix incorrect suggestion for `excessive_precision` +* Fix incorrect suggestion for `expect_fun_call` +* Fix incorrect suggestion for `get_unwrap` +* Fix incorrect suggestion for `useless_format` +* `fn_to_numeric_cast_with_truncation` lint can be disabled again +* Improve suggestions for `manual_memcpy` +* Improve help message for `needless_lifetimes` + +## Rust 1.30 + +Released 2018-10-25 + +[14207503...125907ad](https://github.com/rust-lang/rust-clippy/compare/14207503...125907ad) + +* Deprecate `assign_ops` lint +* New lints: [`mistyped_literal_suffixes`], [`ptr_offset_with_cast`], + [`needless_collect`], [`copy_iterator`] +* `cargo clippy -V` now includes the Clippy commit hash of the Rust + Clippy component +* Fix ICE in `implicit_hasher` +* Fix ICE when encountering `println!("{}" a);` +* Fix ICE when encountering a macro call in match statements +* Fix false positive in `default_trait_access` +* Fix false positive in `trivially_copy_pass_by_ref` +* Fix false positive in `similar_names` +* Fix false positive in `redundant_field_name` +* Fix false positive in `expect_fun_call` +* Fix false negative in `identity_conversion` +* Fix false negative in `explicit_counter_loop` +* Fix `range_plus_one` suggestion and false negative +* `print_with_newline` / `write_with_newline`: don't warn about string with several `\n`s in them +* Fix `useless_attribute` to also whitelist `unused_extern_crates` +* Fix incorrect suggestion for `single_char_pattern` +* Improve suggestion for `identity_conversion` lint +* Move `explicit_iter_loop` and `explicit_into_iter_loop` from `style` group to `pedantic` +* Move `range_plus_one` and `range_minus_one` from `nursery` group to `complexity` +* Move `shadow_unrelated` from `restriction` group to `pedantic` +* Move `indexing_slicing` from `pedantic` group to `restriction` + +## Rust 1.29 + +Released 2018-09-13 + +[v0.0.212...14207503](https://github.com/rust-lang/rust-clippy/compare/v0.0.212...14207503) + +* :tada: :tada: **Rust 1.29 is the first stable Rust that includes a bundled Clippy** :tada: + :tada: + You can now run `rustup component add clippy-preview` and then `cargo + clippy` to run Clippy. This should put an end to the continuous nightly + upgrades for Clippy users. +* Clippy now follows the Rust versioning scheme instead of its own +* Fix ICE when encountering a `while let (..) = x.iter()` construct +* Fix false positives in `use_self` +* Fix false positive in `trivially_copy_pass_by_ref` +* Fix false positive in `useless_attribute` lint +* Fix false positive in `print_literal` +* Fix `use_self` regressions +* Improve lint message for `neg_cmp_op_on_partial_ord` +* Improve suggestion highlight for `single_char_pattern` +* Improve suggestions for various print/write macro lints +* Improve website header + +## 0.0.212 (2018-07-10) +* Rustup to *rustc 1.29.0-nightly (e06c87544 2018-07-06)* + +## 0.0.211 +* Rustup to *rustc 1.28.0-nightly (e3bf634e0 2018-06-28)* + +## 0.0.210 +* Rustup to *rustc 1.28.0-nightly (01cc982e9 2018-06-24)* + +## 0.0.209 +* Rustup to *rustc 1.28.0-nightly (523097979 2018-06-18)* + +## 0.0.208 +* Rustup to *rustc 1.28.0-nightly (86a8f1a63 2018-06-17)* + +## 0.0.207 +* Rustup to *rustc 1.28.0-nightly (2a0062974 2018-06-09)* + +## 0.0.206 +* Rustup to *rustc 1.28.0-nightly (5bf68db6e 2018-05-28)* + +## 0.0.205 +* Rustup to *rustc 1.28.0-nightly (990d8aa74 2018-05-25)* +* Rename `unused_lifetimes` to `extra_unused_lifetimes` because of naming conflict with new rustc lint + +## 0.0.204 +* Rustup to *rustc 1.28.0-nightly (71e87be38 2018-05-22)* + +## 0.0.203 +* Rustup to *rustc 1.28.0-nightly (a3085756e 2018-05-19)* +* Clippy attributes are now of the form `clippy::cyclomatic_complexity` instead of `clippy(cyclomatic_complexity)` + +## 0.0.202 +* Rustup to *rustc 1.28.0-nightly (952f344cd 2018-05-18)* + +## 0.0.201 +* Rustup to *rustc 1.27.0-nightly (2f2a11dfc 2018-05-16)* + +## 0.0.200 +* Rustup to *rustc 1.27.0-nightly (9fae15374 2018-05-13)* + +## 0.0.199 +* Rustup to *rustc 1.27.0-nightly (ff2ac35db 2018-05-12)* + +## 0.0.198 +* Rustup to *rustc 1.27.0-nightly (acd3871ba 2018-05-10)* + +## 0.0.197 +* Rustup to *rustc 1.27.0-nightly (428ea5f6b 2018-05-06)* + +## 0.0.196 +* Rustup to *rustc 1.27.0-nightly (e82261dfb 2018-05-03)* + +## 0.0.195 +* Rustup to *rustc 1.27.0-nightly (ac3c2288f 2018-04-18)* + +## 0.0.194 +* Rustup to *rustc 1.27.0-nightly (bd40cbbe1 2018-04-14)* +* New lints: [`cast_ptr_alignment`], [`transmute_ptr_to_ptr`], [`write_literal`], [`write_with_newline`], [`writeln_empty_string`] + +## 0.0.193 +* Rustup to *rustc 1.27.0-nightly (eeea94c11 2018-04-06)* + +## 0.0.192 +* Rustup to *rustc 1.27.0-nightly (fb44b4c0e 2018-04-04)* +* New lint: [`print_literal`] + +## 0.0.191 +* Rustup to *rustc 1.26.0-nightly (ae544ee1c 2018-03-29)* +* Lint audit; categorize lints as style, correctness, complexity, pedantic, nursery, restriction. + +## 0.0.190 +* Fix a bunch of intermittent cargo bugs + +## 0.0.189 +* Rustup to *rustc 1.26.0-nightly (5508b2714 2018-03-18)* + +## 0.0.188 +* Rustup to *rustc 1.26.0-nightly (392645394 2018-03-15)* +* New lint: [`while_immutable_condition`] + +## 0.0.187 +* Rustup to *rustc 1.26.0-nightly (322d7f7b9 2018-02-25)* +* New lints: [`redundant_field_names`], [`suspicious_arithmetic_impl`], [`suspicious_op_assign_impl`] + +## 0.0.186 +* Rustup to *rustc 1.25.0-nightly (0c6091fbd 2018-02-04)* +* Various false positive fixes + +## 0.0.185 +* Rustup to *rustc 1.25.0-nightly (56733bc9f 2018-02-01)* +* New lint: [`question_mark`] + +## 0.0.184 +* Rustup to *rustc 1.25.0-nightly (90eb44a58 2018-01-29)* +* New lints: [`double_comparisons`], [`empty_line_after_outer_attr`] + +## 0.0.183 +* Rustup to *rustc 1.25.0-nightly (21882aad7 2018-01-28)* +* New lint: [`misaligned_transmute`] + +## 0.0.182 +* Rustup to *rustc 1.25.0-nightly (a0dcecff9 2018-01-24)* +* New lint: [`decimal_literal_representation`] + +## 0.0.181 +* Rustup to *rustc 1.25.0-nightly (97520ccb1 2018-01-21)* +* New lints: [`else_if_without_else`], [`option_option`], [`unit_arg`], [`unnecessary_fold`] +* Removed `unit_expr` +* Various false positive fixes for [`needless_pass_by_value`] + +## 0.0.180 +* Rustup to *rustc 1.25.0-nightly (3f92e8d89 2018-01-14)* + +## 0.0.179 +* Rustup to *rustc 1.25.0-nightly (61452e506 2018-01-09)* + +## 0.0.178 +* Rustup to *rustc 1.25.0-nightly (ee220daca 2018-01-07)* + +## 0.0.177 +* Rustup to *rustc 1.24.0-nightly (250b49205 2017-12-21)* +* New lint: [`match_as_ref`] + +## 0.0.176 +* Rustup to *rustc 1.24.0-nightly (0077d128d 2017-12-14)* + +## 0.0.175 +* Rustup to *rustc 1.24.0-nightly (bb42071f6 2017-12-01)* + +## 0.0.174 +* Rustup to *rustc 1.23.0-nightly (63739ab7b 2017-11-21)* + +## 0.0.173 +* Rustup to *rustc 1.23.0-nightly (33374fa9d 2017-11-20)* + +## 0.0.172 +* Rustup to *rustc 1.23.0-nightly (d0f8e2913 2017-11-16)* + +## 0.0.171 +* Rustup to *rustc 1.23.0-nightly (ff0f5de3b 2017-11-14)* + +## 0.0.170 +* Rustup to *rustc 1.23.0-nightly (d6b06c63a 2017-11-09)* + +## 0.0.169 +* Rustup to *rustc 1.23.0-nightly (3b82e4c74 2017-11-05)* +* New lints: [`just_underscores_and_digits`], [`result_map_unwrap_or_else`], [`transmute_bytes_to_str`] + +## 0.0.168 +* Rustup to *rustc 1.23.0-nightly (f0fe716db 2017-10-30)* + +## 0.0.167 +* Rustup to *rustc 1.23.0-nightly (90ef3372e 2017-10-29)* +* New lints: `const_static_lifetime`, [`erasing_op`], [`fallible_impl_from`], [`println_empty_string`], [`useless_asref`] + +## 0.0.166 +* Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)* +* New lints: [`explicit_write`], [`identity_conversion`], [`implicit_hasher`], [`invalid_ref`], [`option_map_or_none`], + [`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`], + [`transmute_int_to_float`] + +## 0.0.165 +* Rust upgrade to rustc 1.22.0-nightly (0e6f4cf51 2017-09-27) +* New lint: [`mut_range_bound`] + +## 0.0.164 +* Update to *rustc 1.22.0-nightly (6c476ce46 2017-09-25)* +* New lint: [`int_plus_one`] + +## 0.0.163 +* Update to *rustc 1.22.0-nightly (14039a42a 2017-09-22)* + +## 0.0.162 +* Update to *rustc 1.22.0-nightly (0701b37d9 2017-09-18)* +* New lint: [`chars_last_cmp`] +* Improved suggestions for [`needless_borrow`], [`ptr_arg`], + +## 0.0.161 +* Update to *rustc 1.22.0-nightly (539f2083d 2017-09-13)* + +## 0.0.160 +* Update to *rustc 1.22.0-nightly (dd08c3070 2017-09-12)* + +## 0.0.159 +* Update to *rustc 1.22.0-nightly (eba374fb2 2017-09-11)* +* New lint: [`clone_on_ref_ptr`] + +## 0.0.158 +* New lint: [`manual_memcpy`] +* [`cast_lossless`] no longer has redundant parentheses in its suggestions +* Update to *rustc 1.22.0-nightly (dead08cb3 2017-09-08)* + +## 0.0.157 - 2017-09-04 +* Update to *rustc 1.22.0-nightly (981ce7d8d 2017-09-03)* +* New lint: `unit_expr` + +## 0.0.156 - 2017-09-03 +* Update to *rustc 1.22.0-nightly (744dd6c1d 2017-09-02)* + +## 0.0.155 +* Update to *rustc 1.21.0-nightly (c11f689d2 2017-08-29)* +* New lint: [`infinite_iter`], [`maybe_infinite_iter`], [`cast_lossless`] + +## 0.0.154 +* Update to *rustc 1.21.0-nightly (2c0558f63 2017-08-24)* +* Fix [`use_self`] triggering inside derives +* Add support for linting an entire workspace with `cargo clippy --all` +* New lint: [`naive_bytecount`] + +## 0.0.153 +* Update to *rustc 1.21.0-nightly (8c303ed87 2017-08-20)* +* New lint: [`use_self`] + +## 0.0.152 +* Update to *rustc 1.21.0-nightly (df511d554 2017-08-14)* + +## 0.0.151 +* Update to *rustc 1.21.0-nightly (13d94d5fa 2017-08-10)* + +## 0.0.150 +* Update to *rustc 1.21.0-nightly (215e0b10e 2017-08-08)* + +## 0.0.148 +* Update to *rustc 1.21.0-nightly (37c7d0ebb 2017-07-31)* +* New lints: [`unreadable_literal`], [`inconsistent_digit_grouping`], [`large_digit_groups`] + +## 0.0.147 +* Update to *rustc 1.21.0-nightly (aac223f4f 2017-07-30)* + +## 0.0.146 +* Update to *rustc 1.21.0-nightly (52a330969 2017-07-27)* +* Fixes false positives in `inline_always` +* Fixes false negatives in `panic_params` + +## 0.0.145 +* Update to *rustc 1.20.0-nightly (afe145d22 2017-07-23)* + +## 0.0.144 +* Update to *rustc 1.20.0-nightly (086eaa78e 2017-07-15)* + +## 0.0.143 +* Update to *rustc 1.20.0-nightly (d84693b93 2017-07-09)* +* Fix `cargo clippy` crashing on `dylib` projects +* Fix false positives around `nested_while_let` and `never_loop` + +## 0.0.142 +* Update to *rustc 1.20.0-nightly (067971139 2017-07-02)* + +## 0.0.141 +* Rewrite of the `doc_markdown` lint. +* Deprecated [`range_step_by_zero`] +* New lint: [`iterator_step_by_zero`] +* New lint: [`needless_borrowed_reference`] +* Update to *rustc 1.20.0-nightly (69c65d296 2017-06-28)* + +## 0.0.140 - 2017-06-16 +* Update to *rustc 1.19.0-nightly (258ae6dd9 2017-06-15)* + +## 0.0.139 — 2017-06-10 +* Update to *rustc 1.19.0-nightly (4bf5c99af 2017-06-10)* +* Fix bugs with for loop desugaring +* Check for [`AsRef`]/[`AsMut`] arguments in [`wrong_self_convention`] + +## 0.0.138 — 2017-06-05 +* Update to *rustc 1.19.0-nightly (0418fa9d3 2017-06-04)* + +## 0.0.137 — 2017-06-05 +* Update to *rustc 1.19.0-nightly (6684d176c 2017-06-03)* + +## 0.0.136 — 2017—05—26 +* Update to *rustc 1.19.0-nightly (557967766 2017-05-26)* + +## 0.0.135 — 2017—05—24 +* Update to *rustc 1.19.0-nightly (5b13bff52 2017-05-23)* + +## 0.0.134 — 2017—05—19 +* Update to *rustc 1.19.0-nightly (0ed1ec9f9 2017-05-18)* + +## 0.0.133 — 2017—05—14 +* Update to *rustc 1.19.0-nightly (826d8f385 2017-05-13)* + +## 0.0.132 — 2017—05—05 +* Fix various bugs and some ices + +## 0.0.131 — 2017—05—04 +* Update to *rustc 1.19.0-nightly (2d4ed8e0c 2017-05-03)* + +## 0.0.130 — 2017—05—03 +* Update to *rustc 1.19.0-nightly (6a5fc9eec 2017-05-02)* + +## 0.0.129 — 2017-05-01 +* Update to *rustc 1.19.0-nightly (06fb4d256 2017-04-30)* + +## 0.0.128 — 2017-04-28 +* Update to *rustc 1.18.0-nightly (94e884b63 2017-04-27)* + +## 0.0.127 — 2017-04-27 +* Update to *rustc 1.18.0-nightly (036983201 2017-04-26)* +* New lint: [`needless_continue`] + +## 0.0.126 — 2017-04-24 +* Update to *rustc 1.18.0-nightly (2bd4b5c6d 2017-04-23)* + +## 0.0.125 — 2017-04-19 +* Update to *rustc 1.18.0-nightly (9f2abadca 2017-04-18)* + +## 0.0.124 — 2017-04-16 +* Update to *rustc 1.18.0-nightly (d5cf1cb64 2017-04-15)* + +## 0.0.123 — 2017-04-07 +* Fix various false positives + +## 0.0.122 — 2017-04-07 +* Rustup to *rustc 1.18.0-nightly (91ae22a01 2017-04-05)* +* New lint: [`op_ref`] + +## 0.0.121 — 2017-03-21 +* Rustup to *rustc 1.17.0-nightly (134c4a0f0 2017-03-20)* + +## 0.0.120 — 2017-03-17 +* Rustup to *rustc 1.17.0-nightly (0aeb9c129 2017-03-15)* + +## 0.0.119 — 2017-03-13 +* Rustup to *rustc 1.17.0-nightly (824c9ebbd 2017-03-12)* + +## 0.0.118 — 2017-03-05 +* Rustup to *rustc 1.17.0-nightly (b1e31766d 2017-03-03)* + +## 0.0.117 — 2017-03-01 +* Rustup to *rustc 1.17.0-nightly (be760566c 2017-02-28)* + +## 0.0.116 — 2017-02-28 +* Fix `cargo clippy` on 64 bit windows systems + +## 0.0.115 — 2017-02-27 +* Rustup to *rustc 1.17.0-nightly (60a0edc6c 2017-02-26)* +* New lints: [`zero_ptr`], [`never_loop`], [`mut_from_ref`] + +## 0.0.114 — 2017-02-08 +* Rustup to *rustc 1.17.0-nightly (c49d10207 2017-02-07)* +* Tests are now ui tests (testing the exact output of rustc) + +## 0.0.113 — 2017-02-04 +* Rustup to *rustc 1.16.0-nightly (eedaa94e3 2017-02-02)* +* New lint: [`large_enum_variant`] +* `explicit_into_iter_loop` provides suggestions + +## 0.0.112 — 2017-01-27 +* Rustup to *rustc 1.16.0-nightly (df8debf6d 2017-01-25)* + +## 0.0.111 — 2017-01-21 +* Rustup to *rustc 1.16.0-nightly (a52da95ce 2017-01-20)* + +## 0.0.110 — 2017-01-20 +* Add badges and categories to `Cargo.toml` + +## 0.0.109 — 2017-01-19 +* Update to *rustc 1.16.0-nightly (c07a6ae77 2017-01-17)* + +## 0.0.108 — 2017-01-12 +* Update to *rustc 1.16.0-nightly (2782e8f8f 2017-01-12)* + +## 0.0.107 — 2017-01-11 +* Update regex dependency +* Fix FP when matching `&&mut` by `&ref` +* Reintroduce `for (_, x) in &mut hash_map` -> `for x in hash_map.values_mut()` +* New lints: [`unused_io_amount`], [`forget_ref`], [`short_circuit_statement`] + +## 0.0.106 — 2017-01-04 +* Fix FP introduced by rustup in [`wrong_self_convention`] + +## 0.0.105 — 2017-01-04 +* Update to *rustc 1.16.0-nightly (468227129 2017-01-03)* +* New lints: [`deref_addrof`], [`double_parens`], [`pub_enum_variant_names`] +* Fix suggestion in [`new_without_default`] +* FP fix in [`absurd_extreme_comparisons`] + +## 0.0.104 — 2016-12-15 +* Update to *rustc 1.15.0-nightly (8f02c429a 2016-12-15)* + +## 0.0.103 — 2016-11-25 +* Update to *rustc 1.15.0-nightly (d5814b03e 2016-11-23)* + +## 0.0.102 — 2016-11-24 +* Update to *rustc 1.15.0-nightly (3bf2be9ce 2016-11-22)* + +## 0.0.101 — 2016-11-23 +* Update to *rustc 1.15.0-nightly (7b3eeea22 2016-11-21)* +* New lint: [`string_extend_chars`] + +## 0.0.100 — 2016-11-20 +* Update to *rustc 1.15.0-nightly (ac635aa95 2016-11-18)* + +## 0.0.99 — 2016-11-18 +* Update to rustc 1.15.0-nightly (0ed951993 2016-11-14) +* New lint: [`get_unwrap`] + +## 0.0.98 — 2016-11-08 +* Fixes an issue due to a change in how cargo handles `--sysroot`, which broke `cargo clippy` + +## 0.0.97 — 2016-11-03 +* For convenience, `cargo clippy` defines a `cargo-clippy` feature. This was + previously added for a short time under the name `clippy` but removed for + compatibility. +* `cargo clippy --help` is more helping (and less helpful :smile:) +* Rustup to *rustc 1.14.0-nightly (5665bdf3e 2016-11-02)* +* New lints: [`if_let_redundant_pattern_matching`], [`partialeq_ne_impl`] + +## 0.0.96 — 2016-10-22 +* Rustup to *rustc 1.14.0-nightly (f09420685 2016-10-20)* +* New lint: [`iter_skip_next`] + +## 0.0.95 — 2016-10-06 +* Rustup to *rustc 1.14.0-nightly (3210fd5c2 2016-10-05)* + +## 0.0.94 — 2016-10-04 +* Fixes bustage on Windows due to forbidden directory name + +## 0.0.93 — 2016-10-03 +* Rustup to *rustc 1.14.0-nightly (144af3e97 2016-10-02)* +* [`option_map_unwrap_or`] and [`option_map_unwrap_or_else`] are now + allowed by default. +* New lint: [`explicit_into_iter_loop`] + +## 0.0.92 — 2016-09-30 +* Rustup to *rustc 1.14.0-nightly (289f3a4ca 2016-09-29)* + +## 0.0.91 — 2016-09-28 +* Rustup to *rustc 1.13.0-nightly (d0623cf7b 2016-09-26)* + +## 0.0.90 — 2016-09-09 +* Rustup to *rustc 1.13.0-nightly (f1f40f850 2016-09-09)* + +## 0.0.89 — 2016-09-06 +* Rustup to *rustc 1.13.0-nightly (cbe4de78e 2016-09-05)* + +## 0.0.88 — 2016-09-04 +* Rustup to *rustc 1.13.0-nightly (70598e04f 2016-09-03)* +* The following lints are not new but were only usable through the `clippy` + lint groups: [`filter_next`], [`for_loop_over_option`], + [`for_loop_over_result`] and [`match_overlapping_arm`]. You should now be + able to `#[allow/deny]` them individually and they are available directly + through `cargo clippy`. + +## 0.0.87 — 2016-08-31 +* Rustup to *rustc 1.13.0-nightly (eac41469d 2016-08-30)* +* New lints: [`builtin_type_shadow`] +* Fix FP in [`zero_prefixed_literal`] and `0b`/`0o` + +## 0.0.86 — 2016-08-28 +* Rustup to *rustc 1.13.0-nightly (a23064af5 2016-08-27)* +* New lints: [`missing_docs_in_private_items`], [`zero_prefixed_literal`] + +## 0.0.85 — 2016-08-19 +* Fix ICE with [`useless_attribute`] +* [`useless_attribute`] ignores `unused_imports` on `use` statements + +## 0.0.84 — 2016-08-18 +* Rustup to *rustc 1.13.0-nightly (aef6971ca 2016-08-17)* + +## 0.0.83 — 2016-08-17 +* Rustup to *rustc 1.12.0-nightly (1bf5fa326 2016-08-16)* +* New lints: [`print_with_newline`], [`useless_attribute`] + +## 0.0.82 — 2016-08-17 +* Rustup to *rustc 1.12.0-nightly (197be89f3 2016-08-15)* +* New lint: [`module_inception`] + +## 0.0.81 — 2016-08-14 +* Rustup to *rustc 1.12.0-nightly (1deb02ea6 2016-08-12)* +* New lints: [`eval_order_dependence`], [`mixed_case_hex_literals`], [`unseparated_literal_suffix`] +* False positive fix in [`too_many_arguments`] +* Addition of functionality to [`needless_borrow`] +* Suggestions for [`clone_on_copy`] +* Bug fix in [`wrong_self_convention`] +* Doc improvements + +## 0.0.80 — 2016-07-31 +* Rustup to *rustc 1.12.0-nightly (1225e122f 2016-07-30)* +* New lints: [`misrefactored_assign_op`], [`serde_api_misuse`] + +## 0.0.79 — 2016-07-10 +* Rustup to *rustc 1.12.0-nightly (f93aaf84c 2016-07-09)* +* Major suggestions refactoring + +## 0.0.78 — 2016-07-02 +* Rustup to *rustc 1.11.0-nightly (01411937f 2016-07-01)* +* New lints: [`wrong_transmute`], [`double_neg`], [`filter_map`] +* For compatibility, `cargo clippy` does not defines the `clippy` feature + introduced in 0.0.76 anymore +* [`collapsible_if`] now considers `if let` + +## 0.0.77 — 2016-06-21 +* Rustup to *rustc 1.11.0-nightly (5522e678b 2016-06-20)* +* New lints: `stutter` and [`iter_nth`] + +## 0.0.76 — 2016-06-10 +* Rustup to *rustc 1.11.0-nightly (7d2f75a95 2016-06-09)* +* `cargo clippy` now automatically defines the `clippy` feature +* New lint: [`not_unsafe_ptr_arg_deref`] + +## 0.0.75 — 2016-06-08 +* Rustup to *rustc 1.11.0-nightly (763f9234b 2016-06-06)* + +## 0.0.74 — 2016-06-07 +* Fix bug with `cargo-clippy` JSON parsing +* Add the `CLIPPY_DISABLE_DOCS_LINKS` environment variable to deactivate the + “for further information visit *lint-link*” message. + +## 0.0.73 — 2016-06-05 +* Fix false positives in [`useless_let_if_seq`] + +## 0.0.72 — 2016-06-04 +* Fix false positives in [`useless_let_if_seq`] + +## 0.0.71 — 2016-05-31 +* Rustup to *rustc 1.11.0-nightly (a967611d8 2016-05-30)* +* New lint: [`useless_let_if_seq`] + +## 0.0.70 — 2016-05-28 +* Rustup to *rustc 1.10.0-nightly (7bddce693 2016-05-27)* +* [`invalid_regex`] and [`trivial_regex`] can now warn on `RegexSet::new`, + `RegexBuilder::new` and byte regexes + +## 0.0.69 — 2016-05-20 +* Rustup to *rustc 1.10.0-nightly (476fe6eef 2016-05-21)* +* [`used_underscore_binding`] has been made `Allow` temporarily + +## 0.0.68 — 2016-05-17 +* Rustup to *rustc 1.10.0-nightly (cd6a40017 2016-05-16)* +* New lint: [`unnecessary_operation`] + +## 0.0.67 — 2016-05-12 +* Rustup to *rustc 1.10.0-nightly (22ac88f1a 2016-05-11)* + +## 0.0.66 — 2016-05-11 +* New `cargo clippy` subcommand +* New lints: [`assign_op_pattern`], [`assign_ops`], [`needless_borrow`] + +## 0.0.65 — 2016-05-08 +* Rustup to *rustc 1.10.0-nightly (62e2b2fb7 2016-05-06)* +* New lints: [`float_arithmetic`], [`integer_arithmetic`] + +## 0.0.64 — 2016-04-26 +* Rustup to *rustc 1.10.0-nightly (645dd013a 2016-04-24)* +* New lints: [`temporary_cstring_as_ptr`], [`unsafe_removed_from_name`], and [`mem_forget`] + +## 0.0.63 — 2016-04-08 +* Rustup to *rustc 1.9.0-nightly (7979dd608 2016-04-07)* + +## 0.0.62 — 2016-04-07 +* Rustup to *rustc 1.9.0-nightly (bf5da36f1 2016-04-06)* + +## 0.0.61 — 2016-04-03 +* Rustup to *rustc 1.9.0-nightly (5ab11d72c 2016-04-02)* +* New lint: [`invalid_upcast_comparisons`] + +## 0.0.60 — 2016-04-01 +* Rustup to *rustc 1.9.0-nightly (e1195c24b 2016-03-31)* + +## 0.0.59 — 2016-03-31 +* Rustup to *rustc 1.9.0-nightly (30a3849f2 2016-03-30)* +* New lints: [`logic_bug`], [`nonminimal_bool`] +* Fixed: [`match_same_arms`] now ignores arms with guards +* Improved: [`useless_vec`] now warns on `for … in vec![…]` + +## 0.0.58 — 2016-03-27 +* Rustup to *rustc 1.9.0-nightly (d5a91e695 2016-03-26)* +* New lint: [`doc_markdown`] + +## 0.0.57 — 2016-03-27 +* Update to *rustc 1.9.0-nightly (a1e29daf1 2016-03-25)* +* Deprecated lints: [`str_to_string`], [`string_to_string`], [`unstable_as_slice`], [`unstable_as_mut_slice`] +* New lint: [`crosspointer_transmute`] + +## 0.0.56 — 2016-03-23 +* Update to *rustc 1.9.0-nightly (0dcc413e4 2016-03-22)* +* New lints: [`many_single_char_names`] and [`similar_names`] + +## 0.0.55 — 2016-03-21 +* Update to *rustc 1.9.0-nightly (02310fd31 2016-03-19)* + +## 0.0.54 — 2016-03-16 +* Update to *rustc 1.9.0-nightly (c66d2380a 2016-03-15)* + +## 0.0.53 — 2016-03-15 +* Add a [configuration file] + +## ~~0.0.52~~ + +## 0.0.51 — 2016-03-13 +* Add `str` to types considered by [`len_zero`] +* New lints: [`indexing_slicing`] + +## 0.0.50 — 2016-03-11 +* Update to *rustc 1.9.0-nightly (c9629d61c 2016-03-10)* + +## 0.0.49 — 2016-03-09 +* Update to *rustc 1.9.0-nightly (eabfc160f 2016-03-08)* +* New lints: [`overflow_check_conditional`], [`unused_label`], [`new_without_default`] + +## 0.0.48 — 2016-03-07 +* Fixed: ICE in [`needless_range_loop`] with globals + +## 0.0.47 — 2016-03-07 +* Update to *rustc 1.9.0-nightly (998a6720b 2016-03-07)* +* New lint: [`redundant_closure_call`] + +[`AsMut`]: https://doc.rust-lang.org/std/convert/trait.AsMut.html +[`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html +[configuration file]: ./rust-clippy#configuration +[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665 +[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md + + + +[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons +[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped +[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant +[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions +[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants +[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern +[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops +[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock +[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask +[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name +[`block_in_if_condition_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_expr +[`block_in_if_condition_stmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#block_in_if_condition_stmt +[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison +[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const +[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box +[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec +[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local +[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow +[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata +[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless +[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation +[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap +[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss +[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment +[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut +[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss +[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 +[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp +[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp +[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions +[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref +[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy +[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr +[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan +[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null +[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned +[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity +[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if +[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain +[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator +[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute +[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro +[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call +[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation +[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const +[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access +[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr +[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver +[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof +[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq +[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression +[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons +[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use +[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg +[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens +[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds +[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy +[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref +[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument +[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec +[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else +[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum +[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr +[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop +[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant +[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use +[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names +[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op +[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op +[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence +[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision +[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit +[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call +[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy +[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop +[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods +[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop +[`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop +[`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write +[`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice +[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes +[`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from +[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file +[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map +[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next +[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next +[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map +[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity +[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic +[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp +[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const +[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons +[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools +[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast +[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation +[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map +[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option +[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result +[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy +[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref +[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send +[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len +[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap +[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion +[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op +[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex +[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching +[`if_let_some_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_some_result +[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else +[`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else +[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond +[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher +[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return +[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub +[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops +[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping +[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing +[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask +[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string +[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match +[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter +[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string +[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display +[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always +[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body +[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one +[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic +[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division +[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array +[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref +[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering +[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref +[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex +[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons +[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements +[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect +[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop +[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth +[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero +[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next +[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero +[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits +[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays +[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups +[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant +[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays +[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty +[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero +[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return +[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock +[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use +[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value +[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist +[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug +[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal +[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports +[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion +[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy +[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic +[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap +[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names +[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone +[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry +[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten +[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref +[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool +[`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items +[`match_overlapping_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_overlapping_arm +[`match_ref_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_ref_pats +[`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms +[`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding +[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm +[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter +[`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum +[`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget +[`mem_replace_option_with_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_none +[`mem_replace_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default +[`mem_replace_with_uninit`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_uninit +[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max +[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute +[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os +[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op +[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn +[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items +[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc +[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items +[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc +[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes +[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals +[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception +[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions +[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic +[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one +[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions +[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl +[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate +[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit +[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref +[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut +[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound +[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type +[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic +[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer +[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount +[`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool +[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow +[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference +[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect +[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue +[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main +[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes +[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value +[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop +[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return +[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update +[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord +[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply +[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop +[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self +[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default +[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect +[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal +[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool +[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options +[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref +[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect +[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref +[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some +[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref +[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap +[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used +[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none +[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn +[`option_map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or +[`option_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or_else +[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option +[`option_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_unwrap_used +[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call +[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing +[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional +[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic +[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params +[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap +[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl +[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite +[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma +[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence +[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal +[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout +[`print_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline +[`println_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string +[`ptr_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg +[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast +[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names +[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark +[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one +[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one +[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero +[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len +[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation +[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone +[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure +[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call +[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls +[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names +[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern +[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching +[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate +[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes +[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref +[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro +[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts +[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs +[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used +[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option +[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn +[`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else +[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used +[`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop +[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition +[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some +[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse +[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse +[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same +[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated +[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement +[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq +[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait +[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names +[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern +[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports +[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match +[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else +[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next +[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization +[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string +[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add +[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign +[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars +[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes +[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string +[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools +[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops +[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl +[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting +[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting +[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map +[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl +[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting +[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments +[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment +[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr +[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some +[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo +[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments +[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines +[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg +[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str +[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int +[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool +[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char +[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float +[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr +[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref +[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null +[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex +[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref +[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err +[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity +[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds +[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc +[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented +[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init +[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg +[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp +[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints +[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast +[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map +[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold +[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed +[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation +[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap +[`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern +[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern +[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable +[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal +[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize +[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name +[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization +[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix +[`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute +[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice +[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice +[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect +[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount +[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label +[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self +[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit +[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug +[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self +[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding +[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref +[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute +[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format +[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq +[`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute +[`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec +[`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box +[`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask +[`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads +[`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons +[`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition +[`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop +[`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator +[`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies +[`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm +[`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports +[`wildcard_in_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_in_or_patterns +[`write_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_literal +[`write_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_with_newline +[`writeln_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string +[`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention +[`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention +[`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute +[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero +[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal +[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr +[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space +[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset + diff --git a/src/tools/clippy/CODE_OF_CONDUCT.md b/src/tools/clippy/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..dec13e44a17f8 --- /dev/null +++ b/src/tools/clippy/CODE_OF_CONDUCT.md @@ -0,0 +1,70 @@ +# The Rust Code of Conduct + +A version of this document [can be found online](https://www.rust-lang.org/conduct.html). + +## Conduct + +**Contact**: [rust-mods@rust-lang.org](mailto:rust-mods@rust-lang.org) + +* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, + gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, + religion, nationality, or other similar characteristic. +* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and + welcoming environment for all. +* Please be kind and courteous. There's no need to be mean or rude. +* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and + numerous costs. There is seldom a right answer. +* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and + see how it works. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We + interpret the term "harassment" as including the definition in the Citizen + Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their + definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. +* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or + made uncomfortable by a community member, please contact one of the channel ops or any of the [Rust moderation + team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a + safe place for you and we've got your back. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. + +## Moderation + + +These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, +please contact the [Rust moderation team][mod_team]. + +1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, + are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. +3. Moderators will first respond to such remarks with a warning. +4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. +5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. +6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended + party a genuine apology. +7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a + different moderator, **in private**. Complaints about bans in-channel are not allowed. +8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate + situation, they should expect less leeway than others. + +In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically +unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly +if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can +drive people away from the community entirely. + +And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was +they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good +there was something you could've communicated better — remember that it's your responsibility to make your fellow +Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about +cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their +trust. + +The enforcement policies listed above apply to all official Rust venues; including official IRC channels (#rust, +#rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); +GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org +(users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Rust Code of Conduct, please contact the +maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider +explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. + +*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the +[Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* + +[mod_team]: https://www.rust-lang.org/team.html#Moderation-team diff --git a/src/tools/clippy/CONTRIBUTING.md b/src/tools/clippy/CONTRIBUTING.md new file mode 100644 index 0000000000000..50a5ee8bbf3c8 --- /dev/null +++ b/src/tools/clippy/CONTRIBUTING.md @@ -0,0 +1,243 @@ +# Contributing to Clippy + +Hello fellow Rustacean! Great to see your interest in compiler internals and lints! + +**First**: if you're unsure or afraid of _anything_, just ask or submit the issue or pull request anyway. You won't be +yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change +something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that. + +Clippy welcomes contributions from everyone. There are many ways to contribute to Clippy and the following document +explains how you can contribute and how to get started. If you have any questions about contributing or need help with +anything, feel free to ask questions on issues or visit the `#clippy` on [Discord]. + +All contributors are expected to follow the [Rust Code of Conduct]. + +* [Getting started](#getting-started) + * [Finding something to fix/improve](#finding-something-to-fiximprove) +* [Writing code](#writing-code) +* [How Clippy works](#how-clippy-works) +* [Fixing nightly build failures](#fixing-build-failures-caused-by-rust) +* [Issue and PR Triage](#issue-and-pr-triage) +* [Bors and Homu](#bors-and-homu) +* [Contributions](#contributions) + +[Discord]: https://discord.gg/rust-lang +[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct + +## Getting started + +High level approach: + +1. Find something to fix/improve +2. Change code (likely some file in `clippy_lints/src/`) +3. Follow the instructions in the [docs for writing lints](doc/adding_lints.md) such as running the `setup-toolchain.sh` script +4. Run `cargo test` in the root directory and wiggle code until it passes +5. Open a PR (also can be done after 2. if you run into problems) + +### Finding something to fix/improve + +All issues on Clippy are mentored, if you want help with a bug just ask +@Manishearth, @flip1995, @phansch or @yaahc. + +Some issues are easier than others. The [`good first issue`] label can be used to find the easy issues. +If you want to work on an issue, please leave a comment so that we can assign it to you! + +There are also some abandoned PRs, marked with [`S-inactive-closed`]. +Pretty often these PRs are nearly completed and just need some extra steps +(formatting, addressing review comments, ...) to be merged. If you want to +complete such a PR, please leave a comment in the PR and open a new one based +on it. + +Issues marked [`T-AST`] involve simple matching of the syntax tree structure, +and are generally easier than [`T-middle`] issues, which involve types +and resolved paths. + +[`T-AST`] issues will generally need you to match against a predefined syntax structure. +To figure out how this syntax structure is encoded in the AST, it is recommended to run +`rustc -Z ast-json` on an example of the structure and compare with the [nodes in the AST docs]. +Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting]. +But we can make it nest-less by using [if_chain] macro, [like this][nest-less]. + +[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an E-easy issue first. +They are mostly classified as [`E-medium`], since they might be somewhat involved code wise, +but not difficult per-se. + +[`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a +lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of +an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful. + +[`good first issue`]: https://github.com/rust-lang/rust-clippy/labels/good%20first%20issue +[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed +[`T-AST`]: https://github.com/rust-lang/rust-clippy/labels/T-AST +[`T-middle`]: https://github.com/rust-lang/rust-clippy/labels/T-middle +[`E-medium`]: https://github.com/rust-lang/rust-clippy/labels/E-medium +[`ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty +[nodes in the AST docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/ +[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/mem_forget.rs#L29-L43 +[if_chain]: https://docs.rs/if_chain/*/if_chain +[nest-less]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/bit_mask.rs#L124-L150 + +## Writing code + +Have a look at the [docs for writing lints][adding_lints] for more details. + +If you want to add a new lint or change existing ones apart from bugfixing, it's +also a good idea to give the [stability guarantees][rfc_stability] and +[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a +quick read. + +[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md +[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md +[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees +[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories + +## How Clippy works + +[`clippy_lints/src/lib.rs`][lint_crate_entry] imports all the different lint modules and registers in the [`LintStore`]. +For example, the [`else_if_without_else`][else_if_without_else] lint is registered like this: + +```rust +// ./clippy_lints/src/lib.rs + +// ... +pub mod else_if_without_else; +// ... + +pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { + // ... + store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse); + // ... + + store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ + // ... + LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE), + // ... + ]); +} +``` + +The [`rustc_lint::LintStore`][`LintStore`] provides two methods to register lints: +[register_early_pass][reg_early_pass] and [register_late_pass][reg_late_pass]. Both take an object +that implements an [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass] respectively. This is done in +every single lint. It's worth noting that the majority of `clippy_lints/src/lib.rs` is autogenerated by `cargo dev +update_lints`. When you are writing your own lint, you can use that script to save you some time. + +```rust +// ./clippy_lints/src/else_if_without_else.rs + +use rustc_lint::{EarlyLintPass, EarlyContext}; + +// ... + +pub struct ElseIfWithoutElse; + +// ... + +impl EarlyLintPass for ElseIfWithoutElse { + // ... the functions needed, to make the lint work +} +``` + +The difference between `EarlyLintPass` and `LateLintPass` is that the methods of the `EarlyLintPass` trait only provide +AST information. The methods of the `LateLintPass` trait are executed after type checking and contain type information +via the `LateContext` parameter. + +That's why the `else_if_without_else` example uses the `register_early_pass` function. Because the +[actual lint logic][else_if_without_else] does not depend on any type information. + +[lint_crate_entry]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs +[else_if_without_else]: https://github.com/rust-lang/rust-clippy/blob/4253aa7137cb7378acc96133c787e49a345c2b3c/clippy_lints/src/else_if_without_else.rs +[`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html +[reg_early_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_early_pass +[reg_late_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_late_pass +[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html +[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html + +## Fixing build failures caused by Rust + +Clippy will sometimes fail to build from source because building it depends on unstable internal Rust features. Most of +the times we have to adapt to the changes and only very rarely there's an actual bug in Rust. Fixing build failures +caused by Rust updates, can be a good way to learn about Rust internals. + +In order to find out why Clippy does not work properly with a new Rust commit, you can use the [rust-toolstate commit +history][toolstate_commit_history]. You will then have to look for the last commit that contains +`test-pass -> build-fail` or `test-pass -> test-fail` for the `clippy-driver` component. +[Here][toolstate_commit] is an example. + +The commit message contains a link to the PR. The PRs are usually small enough to discover the breaking API change and +if they are bigger, they likely include some discussion that may help you to fix Clippy. + +To check if Clippy is available for a specific target platform, you can check +the [rustup component history][rustup_component_history]. + +If you decide to make Clippy work again with a Rust commit that breaks it, +you probably want to install the latest Rust from master locally and run Clippy +using that version of Rust. + +You can set up the master toolchain by running `./setup-toolchain.sh`. That script will install +[rustup-toolchain-install-master][rtim] and master toolchain, then run `rustup override set master`. + +After fixing the build failure on this repository, we can submit a pull request +to [`rust-lang/rust`] to fix the toolstate. + +To submit a pull request, you should follow these steps: + +```bash +# Assuming you already cloned the rust-lang/rust repo and you're in the correct directory +git submodule update --remote src/tools/clippy +cargo update -p clippy +git add -u +git commit -m "Update Clippy" +./x.py test -i --stage 1 src/tools/clippy # This is optional and should succeed anyway +# Open a PR in rust-lang/rust +``` + +[rustup_component_history]: https://rust-lang.github.io/rustup-components-history +[toolstate_commit_history]: https://github.com/rust-lang-nursery/rust-toolstate/commits/master +[toolstate_commit]: https://github.com/rust-lang-nursery/rust-toolstate/commit/aad74d8294e198a7cf8ac81a91aebb7f3bbcf727 +[rtim]: https://github.com/kennytm/rustup-toolchain-install-master +[`rust-lang/rust`]: https://github.com/rust-lang/rust + +## Issue and PR triage + +Clippy is following the [Rust triage procedure][triage] for issues and pull +requests. + +However, we are a smaller project with all contributors being volunteers +currently. Between writing new lints, fixing issues, reviewing pull requests and +responding to issues there may not always be enough time to stay on top of it +all. + +Our highest priority is fixing [crashes][l-crash] and [bugs][l-bug]. We don't +want Clippy to crash on your code and we want it to be as reliable as the +suggestions from Rust compiler errors. + +## Bors and Homu + +We use a bot powered by [Homu][homu] to help automate testing and landing of pull +requests in Clippy. The bot's username is @bors. + +You can find the Clippy bors queue [here][homu_queue]. + +If you have @bors permissions, you can find an overview of the available +commands [here][homu_instructions]. + +[triage]: https://forge.rust-lang.org/release/triage-procedure.html +[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash%20%3Aboom%3A +[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug%20%3Abeetle%3A +[homu]: https://github.com/rust-lang/homu +[homu_instructions]: https://buildbot2.rust-lang.org/homu/ +[homu_queue]: https://buildbot2.rust-lang.org/homu/queue/clippy + +## Contributions + +Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will +be reviewed by a core contributor (someone with permission to land patches) and either landed in the +main tree or given feedback for changes that would be required. + +All code in this repository is under the [Apache-2.0] or the [MIT] license. + + + +[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0 +[MIT]: https://opensource.org/licenses/MIT diff --git a/src/tools/clippy/COPYRIGHT b/src/tools/clippy/COPYRIGHT new file mode 100644 index 0000000000000..80d64472c70a6 --- /dev/null +++ b/src/tools/clippy/COPYRIGHT @@ -0,0 +1,7 @@ +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. All files in the project carrying such notice may not be +copied, modified, or distributed except according to those terms. diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml new file mode 100644 index 0000000000000..63ce2cd8cad77 --- /dev/null +++ b/src/tools/clippy/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "clippy" +version = "0.0.212" +authors = [ + "Manish Goregaokar ", + "Andre Bogus ", + "Georg Brandl ", + "Martin Carton ", + "Oliver Schneider " +] +description = "A bunch of helpful lints to avoid common pitfalls in Rust" +repository = "https://github.com/rust-lang/rust-clippy" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["clippy", "lint", "plugin"] +categories = ["development-tools", "development-tools::cargo-plugins"] +build = "build.rs" +edition = "2018" +publish = false + +[[bin]] +name = "cargo-clippy" +test = false +path = "src/main.rs" + +[[bin]] +name = "clippy-driver" +path = "src/driver.rs" + +[dependencies] +# begin automatic update +clippy_lints = { version = "0.0.212", path = "clippy_lints" } +# end automatic update +regex = "1" +semver = "0.9" +rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util"} +tempfile = { version = "3.1.0", optional = true } +lazy_static = "1.0" + +[dev-dependencies] +cargo_metadata = "0.9.0" +compiletest_rs = { version = "0.5.0", features = ["tmp"] } +tester = "0.7" +lazy_static = "1.0" +clippy-mini-macro-test = { version = "0.2", path = "mini-macro" } +serde = { version = "1.0", features = ["derive"] } +derive-new = "0.5" + +# A noop dependency that changes in the Rust repository, it's a bit of a hack. +# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` +# for more information. +rustc-workspace-hack = "1.0.0" + +[build-dependencies] +rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util"} + +[features] +deny-warnings = [] +integration = ["tempfile"] diff --git a/src/tools/clippy/LICENSE-APACHE b/src/tools/clippy/LICENSE-APACHE new file mode 100644 index 0000000000000..d821a4de2bed8 --- /dev/null +++ b/src/tools/clippy/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/tools/clippy/LICENSE-MIT b/src/tools/clippy/LICENSE-MIT new file mode 100644 index 0000000000000..b7c70dd4026d9 --- /dev/null +++ b/src/tools/clippy/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2014-2020 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/tools/clippy/README.md b/src/tools/clippy/README.md new file mode 100644 index 0000000000000..222b81023a770 --- /dev/null +++ b/src/tools/clippy/README.md @@ -0,0 +1,193 @@ +# Clippy + +[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test%22+event%3Apush+branch%3Aauto) +[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) + +A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. + +[There are over 350 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) + +We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you: + +* `clippy::all` (everything that is on by default: all the categories below except for `nursery`, `pedantic`, and `cargo`) +* `clippy::correctness` (code that is just **outright wrong** or **very very useless**, causes hard errors by default) +* `clippy::style` (code that should be written in a more idiomatic way) +* `clippy::complexity` (code that does something simple but in a complex way) +* `clippy::perf` (code that can be written in a faster way) +* `clippy::pedantic` (lints which are rather strict, off by default) +* `clippy::nursery` (new lints that aren't quite ready yet, off by default) +* `clippy::cargo` (checks against the cargo manifest, off by default) + +More to come, please [file an issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! + +Only the following of those categories are enabled by default: + +* `clippy::style` +* `clippy::correctness` +* `clippy::complexity` +* `clippy::perf` + +Other categories need to be enabled in order for their lints to be executed. + +The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also contains "restriction lints", which are +for things which are usually not considered "bad", but may be useful to turn on in specific cases. These should be used +very selectively, if at all. + +Table of contents: + +* [Usage instructions](#usage) +* [Configuration](#configuration) +* [Contributing](#contributing) +* [License](#license) + +## Usage + +Since this is a tool for helping the developer of a library or application +write better code, it is recommended not to include Clippy as a hard dependency. +Options include using it as an optional dependency, as a cargo subcommand, or +as an included feature during build. These options are detailed below. + +### As a cargo subcommand (`cargo clippy`) + +One way to use Clippy is by installing Clippy through rustup as a cargo +subcommand. + +#### Step 1: Install rustup + +You can install [rustup](https://rustup.rs/) on supported platforms. This will help +us install Clippy and its dependencies. + +If you already have rustup installed, update to ensure you have the latest +rustup and compiler: + +```terminal +rustup update +``` + +#### Step 2: Install Clippy + +Once you have rustup and the latest stable release (at least Rust 1.29) installed, run the following command: + +```terminal +rustup component add clippy +``` +If it says that it can't find the `clippy` component, please run `rustup self update`. + +#### Step 3: Run Clippy + +Now you can run Clippy by invoking the following command: + +```terminal +cargo clippy +``` + +#### Automatically applying Clippy suggestions + +Clippy can automatically apply some lint suggestions. +Note that this is still experimental and only supported on the nightly channel: + +```terminal +cargo clippy --fix -Z unstable-options +``` + +### Running Clippy from the command line without installing it + +To have cargo compile your crate with Clippy without Clippy installation +in your code, you can use: + +```terminal +cargo run --bin cargo-clippy --manifest-path=path_to_clippys_Cargo.toml +``` + +*Note:* Be sure that Clippy was compiled with the same version of rustc that cargo invokes here! + +### Travis CI + +You can add Clippy to Travis CI in the same way you use it locally: + +```yml +language: rust +rust: + - stable + - beta +before_script: + - rustup component add clippy +script: + - cargo clippy + # if you want the build job to fail when encountering warnings, use + - cargo clippy -- -D warnings + # in order to also check tests and non-default crate features, use + - cargo clippy --all-targets --all-features -- -D warnings + - cargo test + # etc. +``` + +If you are on nightly, It might happen that Clippy is not available for a certain nightly release. +In this case you can try to conditionally install Clippy from the Git repo. + +```yaml +language: rust +rust: + - nightly +before_script: + - rustup component add clippy --toolchain=nightly || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy + # etc. +``` + +Note that adding `-D warnings` will cause your build to fail if **any** warnings are found in your code. +That includes warnings found by rustc (e.g. `dead_code`, etc.). If you want to avoid this and only cause +an error for Clippy warnings, use `#![deny(clippy::all)]` in your code or `-D clippy::all` on the command +line. (You can swap `clippy::all` with the specific lint category you are targeting.) + +## Configuration + +Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a basic `variable = +value` mapping eg. + +```toml +blacklisted-names = ["toto", "tata", "titi"] +cognitive-complexity-threshold = 30 +``` + +See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which +lints can be configured and the meaning of the variables. + +To deactivate the “for further information visit *lint-link*” message you can +define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable. + +### Allowing/denying lints + +You can add options to your code to `allow`/`warn`/`deny` Clippy lints: + +* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`) + +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, + `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive + lints prone to false positives. + +* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) + +* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. + +Note: `deny` produces errors instead of warnings. + +If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra +flags to Clippy during the run: `cargo clippy -- -A clippy::lint_name` will run Clippy with `lint_name` disabled and +`cargo clippy -- -W clippy::lint_name` will run it with that enabled. This also works with lint groups. For example you +can run Clippy with warnings for all lints enabled: `cargo clippy -- -W clippy::pedantic` +If you care only about a single lint, you can allow all others and then explicitly reenable +the lint(s) you are interested in: `cargo clippy -- -Aclippy::all -Wclippy::useless_format -Wclippy::...` + +## Contributing + +If you want to contribute to Clippy, you can find more information in [CONTRIBUTING.md](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md). + +## License + +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. Files in the project may not be +copied, modified, or distributed except according to those terms. diff --git a/src/tools/clippy/build.rs b/src/tools/clippy/build.rs new file mode 100644 index 0000000000000..018375dbada87 --- /dev/null +++ b/src/tools/clippy/build.rs @@ -0,0 +1,19 @@ +fn main() { + // Forward the profile to the main compilation + println!("cargo:rustc-env=PROFILE={}", std::env::var("PROFILE").unwrap()); + // Don't rebuild even if nothing changed + println!("cargo:rerun-if-changed=build.rs"); + // forward git repo hashes we build at + println!( + "cargo:rustc-env=GIT_HASH={}", + rustc_tools_util::get_commit_hash().unwrap_or_default() + ); + println!( + "cargo:rustc-env=COMMIT_DATE={}", + rustc_tools_util::get_commit_date().unwrap_or_default() + ); + println!( + "cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}", + rustc_tools_util::get_channel().unwrap_or_default() + ); +} diff --git a/src/tools/clippy/clippy_dev/Cargo.toml b/src/tools/clippy/clippy_dev/Cargo.toml new file mode 100644 index 0000000000000..c861efc8afb50 --- /dev/null +++ b/src/tools/clippy/clippy_dev/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "clippy_dev" +version = "0.0.1" +authors = ["Philipp Hansch "] +edition = "2018" + +[dependencies] +bytecount = "0.6" +clap = "2.33" +itertools = "0.9" +regex = "1" +lazy_static = "1.0" +shell-escape = "0.1" +walkdir = "2" + +[features] +deny-warnings = [] diff --git a/src/tools/clippy/clippy_dev/src/fmt.rs b/src/tools/clippy/clippy_dev/src/fmt.rs new file mode 100644 index 0000000000000..6ae3f58c1f2ad --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/fmt.rs @@ -0,0 +1,175 @@ +use crate::clippy_project_root; +use shell_escape::escape; +use std::ffi::OsStr; +use std::io; +use std::path::Path; +use std::process::{self, Command}; +use walkdir::WalkDir; + +#[derive(Debug)] +pub enum CliError { + CommandFailed(String), + IoError(io::Error), + RustfmtNotInstalled, + WalkDirError(walkdir::Error), +} + +impl From for CliError { + fn from(error: io::Error) -> Self { + Self::IoError(error) + } +} + +impl From for CliError { + fn from(error: walkdir::Error) -> Self { + Self::WalkDirError(error) + } +} + +struct FmtContext { + check: bool, + verbose: bool, +} + +pub fn run(check: bool, verbose: bool) { + fn try_run(context: &FmtContext) -> Result { + let mut success = true; + + let project_root = clippy_project_root(); + + rustfmt_test(context)?; + + success &= cargo_fmt(context, project_root.as_path())?; + success &= cargo_fmt(context, &project_root.join("clippy_dev"))?; + success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?; + + for entry in WalkDir::new(project_root.join("tests")) { + let entry = entry?; + let path = entry.path(); + + if path.extension() != Some("rs".as_ref()) + || entry.file_name() == "ice-3891.rs" + // Avoid rustfmt bug rust-lang/rustfmt#1873 + || cfg!(windows) && entry.file_name() == "implicit_hasher.rs" + { + continue; + } + + success &= rustfmt(context, &path)?; + } + + Ok(success) + } + + fn output_err(err: CliError) { + match err { + CliError::CommandFailed(command) => { + eprintln!("error: A command failed! `{}`", command); + }, + CliError::IoError(err) => { + eprintln!("error: {}", err); + }, + CliError::RustfmtNotInstalled => { + eprintln!("error: rustfmt nightly is not installed."); + }, + CliError::WalkDirError(err) => { + eprintln!("error: {}", err); + }, + } + } + + let context = FmtContext { check, verbose }; + let result = try_run(&context); + let code = match result { + Ok(true) => 0, + Ok(false) => { + eprintln!(); + eprintln!("Formatting check failed."); + eprintln!("Run `cargo dev fmt` to update formatting."); + 1 + }, + Err(err) => { + output_err(err); + 1 + }, + }; + process::exit(code); +} + +fn format_command(program: impl AsRef, dir: impl AsRef, args: &[impl AsRef]) -> String { + let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).collect(); + + format!( + "cd {} && {} {}", + escape(dir.as_ref().to_string_lossy()), + escape(program.as_ref().to_string_lossy()), + arg_display.join(" ") + ) +} + +fn exec( + context: &FmtContext, + program: impl AsRef, + dir: impl AsRef, + args: &[impl AsRef], +) -> Result { + if context.verbose { + println!("{}", format_command(&program, &dir, args)); + } + + let mut child = Command::new(&program).current_dir(&dir).args(args.iter()).spawn()?; + let code = child.wait()?; + let success = code.success(); + + if !context.check && !success { + return Err(CliError::CommandFailed(format_command(&program, &dir, args))); + } + + Ok(success) +} + +fn cargo_fmt(context: &FmtContext, path: &Path) -> Result { + let mut args = vec!["+nightly", "fmt", "--all"]; + if context.check { + args.push("--"); + args.push("--check"); + } + let success = exec(context, "cargo", path, &args)?; + + Ok(success) +} + +fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { + let program = "rustfmt"; + let dir = std::env::current_dir()?; + let args = &["+nightly", "--version"]; + + if context.verbose { + println!("{}", format_command(&program, &dir, args)); + } + + let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?; + + if output.status.success() { + Ok(()) + } else if std::str::from_utf8(&output.stderr) + .unwrap_or("") + .starts_with("error: 'rustfmt' is not installed") + { + Err(CliError::RustfmtNotInstalled) + } else { + Err(CliError::CommandFailed(format_command(&program, &dir, args))) + } +} + +fn rustfmt(context: &FmtContext, path: &Path) -> Result { + let mut args = vec!["+nightly".as_ref(), path.as_os_str()]; + if context.check { + args.push("--check".as_ref()); + } + let success = exec(context, "rustfmt", std::env::current_dir()?, &args)?; + if !success { + eprintln!("rustfmt failed on {}", path.display()); + } + Ok(success) +} diff --git a/src/tools/clippy/clippy_dev/src/lib.rs b/src/tools/clippy/clippy_dev/src/lib.rs new file mode 100644 index 0000000000000..6fdd282c6849e --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/lib.rs @@ -0,0 +1,524 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] + +use itertools::Itertools; +use lazy_static::lazy_static; +use regex::Regex; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +pub mod fmt; +pub mod new_lint; +pub mod stderr_length_check; +pub mod update_lints; + +lazy_static! { + static ref DEC_CLIPPY_LINT_RE: Regex = Regex::new( + r#"(?x) + declare_clippy_lint!\s*[\{(] + (?:\s+///.*)* + \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* + (?P[a-z_]+)\s*,\s* + "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] + "# + ) + .unwrap(); + static ref DEC_DEPRECATED_LINT_RE: Regex = Regex::new( + r#"(?x) + declare_deprecated_lint!\s*[{(]\s* + (?:\s+///.*)* + \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* + "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] + "# + ) + .unwrap(); + static ref NL_ESCAPE_RE: Regex = Regex::new(r#"\\\n\s*"#).unwrap(); +} + +pub static DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; + +/// Lint data parsed from the Clippy source code. +#[derive(Clone, PartialEq, Debug)] +pub struct Lint { + pub name: String, + pub group: String, + pub desc: String, + pub deprecation: Option, + pub module: String, +} + +impl Lint { + #[must_use] + pub fn new(name: &str, group: &str, desc: &str, deprecation: Option<&str>, module: &str) -> Self { + Self { + name: name.to_lowercase(), + group: group.to_string(), + desc: NL_ESCAPE_RE.replace(&desc.replace("\\\"", "\""), "").to_string(), + deprecation: deprecation.map(ToString::to_string), + module: module.to_string(), + } + } + + /// Returns all non-deprecated lints and non-internal lints + #[must_use] + pub fn usable_lints(lints: &[Self]) -> Vec { + lints + .iter() + .filter(|l| l.deprecation.is_none() && !l.group.starts_with("internal")) + .cloned() + .collect() + } + + /// Returns all internal lints (not `internal_warn` lints) + #[must_use] + pub fn internal_lints(lints: &[Self]) -> Vec { + lints.iter().filter(|l| l.group == "internal").cloned().collect() + } + + /// Returns all deprecated lints + #[must_use] + pub fn deprecated_lints(lints: &[Self]) -> Vec { + lints.iter().filter(|l| l.deprecation.is_some()).cloned().collect() + } + + /// Returns the lints in a `HashMap`, grouped by the different lint groups + #[must_use] + pub fn by_lint_group(lints: impl Iterator) -> HashMap> { + lints.map(|lint| (lint.group.to_string(), lint)).into_group_map() + } +} + +/// Generates the Vec items for `register_lint_group` calls in `clippy_lints/src/lib.rs`. +#[must_use] +pub fn gen_lint_group_list<'a>(lints: impl Iterator) -> Vec { + lints + .map(|l| format!(" LintId::of(&{}::{}),", l.module, l.name.to_uppercase())) + .sorted() + .collect::>() +} + +/// Generates the `pub mod module_name` list in `clippy_lints/src/lib.rs`. +#[must_use] +pub fn gen_modules_list<'a>(lints: impl Iterator) -> Vec { + lints + .map(|l| &l.module) + .unique() + .map(|module| format!("mod {};", module)) + .sorted() + .collect::>() +} + +/// Generates the list of lint links at the bottom of the README +#[must_use] +pub fn gen_changelog_lint_list<'a>(lints: impl Iterator) -> Vec { + lints + .sorted_by_key(|l| &l.name) + .map(|l| format!("[`{}`]: {}#{}", l.name, DOCS_LINK, l.name)) + .collect() +} + +/// Generates the `register_removed` code in `./clippy_lints/src/lib.rs`. +#[must_use] +pub fn gen_deprecated<'a>(lints: impl Iterator) -> Vec { + lints + .flat_map(|l| { + l.deprecation + .clone() + .map(|depr_text| { + vec![ + " store.register_removed(".to_string(), + format!(" \"clippy::{}\",", l.name), + format!(" \"{}\",", depr_text), + " );".to_string(), + ] + }) + .expect("only deprecated lints should be passed") + }) + .collect::>() +} + +#[must_use] +pub fn gen_register_lint_list<'a>(lints: impl Iterator) -> Vec { + let pre = " store.register_lints(&[".to_string(); + let post = " ]);".to_string(); + let mut inner = lints + .map(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) + .sorted() + .collect::>(); + inner.insert(0, pre); + inner.push(post); + inner +} + +/// Gathers all files in `src/clippy_lints` and gathers all lints inside +pub fn gather_all() -> impl Iterator { + lint_files().flat_map(|f| gather_from_file(&f)) +} + +fn gather_from_file(dir_entry: &walkdir::DirEntry) -> impl Iterator { + let content = fs::read_to_string(dir_entry.path()).unwrap(); + let path = dir_entry.path(); + let filename = path.file_stem().unwrap(); + let path_buf = path.with_file_name(filename); + let mut rel_path = path_buf + .strip_prefix(clippy_project_root().join("clippy_lints/src")) + .expect("only files in `clippy_lints/src` should be looked at"); + // If the lints are stored in mod.rs, we get the module name from + // the containing directory: + if filename == "mod" { + rel_path = rel_path.parent().unwrap(); + } + + let module = rel_path + .components() + .map(|c| c.as_os_str().to_str().unwrap()) + .collect::>() + .join("::"); + + parse_contents(&content, &module) +} + +fn parse_contents(content: &str, module: &str) -> impl Iterator { + let lints = DEC_CLIPPY_LINT_RE + .captures_iter(content) + .map(|m| Lint::new(&m["name"], &m["cat"], &m["desc"], None, module)); + let deprecated = DEC_DEPRECATED_LINT_RE + .captures_iter(content) + .map(|m| Lint::new(&m["name"], "Deprecated", &m["desc"], Some(&m["desc"]), module)); + // Removing the `.collect::>().into_iter()` causes some lifetime issues due to the map + lints.chain(deprecated).collect::>().into_iter() +} + +/// Collects all .rs files in the `clippy_lints/src` directory +fn lint_files() -> impl Iterator { + // We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories. + // Otherwise we would not collect all the lints, for example in `clippy_lints/src/methods/`. + let path = clippy_project_root().join("clippy_lints/src"); + WalkDir::new(path) + .into_iter() + .filter_map(Result::ok) + .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) +} + +/// Whether a file has had its text changed or not +#[derive(PartialEq, Debug)] +pub struct FileChange { + pub changed: bool, + pub new_lines: String, +} + +/// Replaces a region in a file delimited by two lines matching regexes. +/// +/// `path` is the relative path to the file on which you want to perform the replacement. +/// +/// See `replace_region_in_text` for documentation of the other options. +pub fn replace_region_in_file( + path: &Path, + start: &str, + end: &str, + replace_start: bool, + write_back: bool, + replacements: F, +) -> FileChange +where + F: FnOnce() -> Vec, +{ + let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.display(), e)); + let file_change = replace_region_in_text(&contents, start, end, replace_start, replacements); + + if write_back { + if let Err(e) = fs::write(path, file_change.new_lines.as_bytes()) { + panic!("Cannot write to {}: {}", path.display(), e); + } + } + file_change +} + +/// Replaces a region in a text delimited by two lines matching regexes. +/// +/// * `text` is the input text on which you want to perform the replacement +/// * `start` is a `&str` that describes the delimiter line before the region you want to replace. +/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. +/// * `end` is a `&str` that describes the delimiter line until where the replacement should happen. +/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. +/// * If `replace_start` is true, the `start` delimiter line is replaced as well. The `end` +/// delimiter line is never replaced. +/// * `replacements` is a closure that has to return a `Vec` which contains the new text. +/// +/// If you want to perform the replacement on files instead of already parsed text, +/// use `replace_region_in_file`. +/// +/// # Example +/// +/// ``` +/// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end"; +/// let result = +/// clippy_dev::replace_region_in_text(the_text, "replace_start", "replace_end", false, || { +/// vec!["a different".to_string(), "text".to_string()] +/// }) +/// .new_lines; +/// assert_eq!("replace_start\na different\ntext\nreplace_end", result); +/// ``` +pub fn replace_region_in_text(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange +where + F: FnOnce() -> Vec, +{ + let replace_it = replacements(); + let mut in_old_region = false; + let mut found = false; + let mut new_lines = vec![]; + let start = Regex::new(start).unwrap(); + let end = Regex::new(end).unwrap(); + + for line in text.lines() { + if in_old_region { + if end.is_match(line) { + in_old_region = false; + new_lines.extend(replace_it.clone()); + new_lines.push(line.to_string()); + } + } else if start.is_match(line) { + if !replace_start { + new_lines.push(line.to_string()); + } + in_old_region = true; + found = true; + } else { + new_lines.push(line.to_string()); + } + } + + if !found { + // This happens if the provided regex in `clippy_dev/src/main.rs` does not match in the + // given text or file. Most likely this is an error on the programmer's side and the Regex + // is incorrect. + eprintln!("error: regex \n{:?}\ndoesn't match. You may have to update it.", start); + std::process::exit(1); + } + + let mut new_lines = new_lines.join("\n"); + if text.ends_with('\n') { + new_lines.push('\n'); + } + let changed = new_lines != text; + FileChange { changed, new_lines } +} + +/// Returns the path to the Clippy project directory +#[must_use] +pub fn clippy_project_root() -> PathBuf { + let current_dir = std::env::current_dir().unwrap(); + for path in current_dir.ancestors() { + let result = std::fs::read_to_string(path.join("Cargo.toml")); + if let Err(err) = &result { + if err.kind() == std::io::ErrorKind::NotFound { + continue; + } + } + + let content = result.unwrap(); + if content.contains("[package]\nname = \"clippy\"") { + return path.to_path_buf(); + } + } + panic!("error: Can't determine root of project. Please run inside a Clippy working dir."); +} + +#[test] +fn test_parse_contents() { + let result: Vec = parse_contents( + r#" +declare_clippy_lint! { + pub PTR_ARG, + style, + "really long \ + text" +} + +declare_clippy_lint!{ + pub DOC_MARKDOWN, + pedantic, + "single line" +} + +/// some doc comment +declare_deprecated_lint! { + pub SHOULD_ASSERT_EQ, + "`assert!()` will be more flexible with RFC 2011" +} + "#, + "module_name", + ) + .collect(); + + let expected = vec![ + Lint::new("ptr_arg", "style", "really long text", None, "module_name"), + Lint::new("doc_markdown", "pedantic", "single line", None, "module_name"), + Lint::new( + "should_assert_eq", + "Deprecated", + "`assert!()` will be more flexible with RFC 2011", + Some("`assert!()` will be more flexible with RFC 2011"), + "module_name", + ), + ]; + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region() { + let text = "\nabc\n123\n789\ndef\nghi"; + let expected = FileChange { + changed: true, + new_lines: "\nabc\nhello world\ndef\nghi".to_string(), + }; + let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, false, || { + vec!["hello world".to_string()] + }); + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region_with_start() { + let text = "\nabc\n123\n789\ndef\nghi"; + let expected = FileChange { + changed: true, + new_lines: "\nhello world\ndef\nghi".to_string(), + }; + let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, true, || { + vec!["hello world".to_string()] + }); + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region_no_changes() { + let text = "123\n456\n789"; + let expected = FileChange { + changed: false, + new_lines: "123\n456\n789".to_string(), + }; + let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, || vec![]); + assert_eq!(expected, result); +} + +#[test] +fn test_usable_lints() { + let lints = vec![ + Lint::new("should_assert_eq", "Deprecated", "abc", Some("Reason"), "module_name"), + Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "internal", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "internal_style", "abc", None, "module_name"), + ]; + let expected = vec![Lint::new( + "should_assert_eq2", + "Not Deprecated", + "abc", + None, + "module_name", + )]; + assert_eq!(expected, Lint::usable_lints(&lints)); +} + +#[test] +fn test_by_lint_group() { + let lints = vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"), + Lint::new("incorrect_match", "group1", "abc", None, "module_name"), + ]; + let mut expected: HashMap> = HashMap::new(); + expected.insert( + "group1".to_string(), + vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("incorrect_match", "group1", "abc", None, "module_name"), + ], + ); + expected.insert( + "group2".to_string(), + vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")], + ); + assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); +} + +#[test] +fn test_gen_changelog_lint_list() { + let lints = vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"), + ]; + let expected = vec![ + format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK.to_string()), + format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK.to_string()), + ]; + assert_eq!(expected, gen_changelog_lint_list(lints.iter())); +} + +#[test] +fn test_gen_deprecated() { + let lints = vec![ + Lint::new( + "should_assert_eq", + "group1", + "abc", + Some("has been superseded by should_assert_eq2"), + "module_name", + ), + Lint::new( + "another_deprecated", + "group2", + "abc", + Some("will be removed"), + "module_name", + ), + ]; + let expected: Vec = vec![ + " store.register_removed(", + " \"clippy::should_assert_eq\",", + " \"has been superseded by should_assert_eq2\",", + " );", + " store.register_removed(", + " \"clippy::another_deprecated\",", + " \"will be removed\",", + " );", + ] + .into_iter() + .map(String::from) + .collect(); + assert_eq!(expected, gen_deprecated(lints.iter())); +} + +#[test] +#[should_panic] +fn test_gen_deprecated_fail() { + let lints = vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")]; + let _ = gen_deprecated(lints.iter()); +} + +#[test] +fn test_gen_modules_list() { + let lints = vec![ + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("incorrect_stuff", "group3", "abc", None, "another_module"), + ]; + let expected = vec!["mod another_module;".to_string(), "mod module_name;".to_string()]; + assert_eq!(expected, gen_modules_list(lints.iter())); +} + +#[test] +fn test_gen_lint_group_list() { + let lints = vec![ + Lint::new("abc", "group1", "abc", None, "module_name"), + Lint::new("should_assert_eq", "group1", "abc", None, "module_name"), + Lint::new("internal", "internal_style", "abc", None, "module_name"), + ]; + let expected = vec![ + " LintId::of(&module_name::ABC),".to_string(), + " LintId::of(&module_name::INTERNAL),".to_string(), + " LintId::of(&module_name::SHOULD_ASSERT_EQ),".to_string(), + ]; + assert_eq!(expected, gen_lint_group_list(lints.iter())); +} diff --git a/src/tools/clippy/clippy_dev/src/main.rs b/src/tools/clippy/clippy_dev/src/main.rs new file mode 100644 index 0000000000000..d99235f7c07a7 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/main.rs @@ -0,0 +1,120 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] + +use clap::{App, Arg, SubCommand}; +use clippy_dev::{fmt, new_lint, stderr_length_check, update_lints}; + +fn main() { + let matches = App::new("Clippy developer tooling") + .subcommand( + SubCommand::with_name("fmt") + .about("Run rustfmt on all projects and tests") + .arg( + Arg::with_name("check") + .long("check") + .help("Use the rustfmt --check option"), + ) + .arg( + Arg::with_name("verbose") + .short("v") + .long("verbose") + .help("Echo commands run"), + ), + ) + .subcommand( + SubCommand::with_name("update_lints") + .about("Updates lint registration and information from the source code") + .long_about( + "Makes sure that:\n \ + * the lint count in README.md is correct\n \ + * the changelog contains markdown link references at the bottom\n \ + * all lint groups include the correct lints\n \ + * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \ + * all lints are registered in the lint store", + ) + .arg(Arg::with_name("print-only").long("print-only").help( + "Print a table of lints to STDOUT. \ + This does not include deprecated and internal lints. \ + (Does not modify any files)", + )) + .arg( + Arg::with_name("check") + .long("check") + .help("Checks that `cargo dev update_lints` has been run. Used on CI."), + ), + ) + .subcommand( + SubCommand::with_name("new_lint") + .about("Create new lint and run `cargo dev update_lints`") + .arg( + Arg::with_name("pass") + .short("p") + .long("pass") + .help("Specify whether the lint runs during the early or late pass") + .takes_value(true) + .possible_values(&["early", "late"]) + .required(true), + ) + .arg( + Arg::with_name("name") + .short("n") + .long("name") + .help("Name of the new lint in snake case, ex: fn_too_long") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("category") + .short("c") + .long("category") + .help("What category the lint belongs to") + .default_value("nursery") + .possible_values(&[ + "style", + "correctness", + "complexity", + "perf", + "pedantic", + "restriction", + "cargo", + "nursery", + "internal", + "internal_warn", + ]) + .takes_value(true), + ), + ) + .subcommand( + SubCommand::with_name("limit_stderr_length") + .about("Ensures that stderr files do not grow longer than a certain amount of lines."), + ) + .get_matches(); + + match matches.subcommand() { + ("fmt", Some(matches)) => { + fmt::run(matches.is_present("check"), matches.is_present("verbose")); + }, + ("update_lints", Some(matches)) => { + if matches.is_present("print-only") { + update_lints::print_lints(); + } else if matches.is_present("check") { + update_lints::run(update_lints::UpdateMode::Check); + } else { + update_lints::run(update_lints::UpdateMode::Change); + } + }, + ("new_lint", Some(matches)) => { + match new_lint::create( + matches.value_of("pass"), + matches.value_of("name"), + matches.value_of("category"), + ) { + Ok(_) => update_lints::run(update_lints::UpdateMode::Change), + Err(e) => eprintln!("Unable to create lint: {}", e), + } + }, + ("limit_stderr_length", _) => { + stderr_length_check::check(); + }, + _ => {}, + } +} diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs new file mode 100644 index 0000000000000..44b2a5383d211 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -0,0 +1,177 @@ +use crate::clippy_project_root; +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::prelude::*; +use std::io::ErrorKind; +use std::path::Path; + +/// Creates files required to implement and test a new lint and runs `update_lints`. +/// +/// # Errors +/// +/// This function errors, if the files couldn't be created +pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> Result<(), io::Error> { + let pass = pass.expect("`pass` argument is validated by clap"); + let lint_name = lint_name.expect("`name` argument is validated by clap"); + let category = category.expect("`category` argument is validated by clap"); + + match open_files(lint_name) { + Ok((mut test_file, mut lint_file)) => { + let (pass_type, pass_lifetimes, pass_import, context_import) = match pass { + "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"), + "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"), + _ => { + unreachable!("`pass_type` should only ever be `early` or `late`!"); + }, + }; + + let camel_case_name = to_camel_case(lint_name); + + if let Err(e) = test_file.write_all(get_test_file_contents(lint_name).as_bytes()) { + return Err(io::Error::new( + ErrorKind::Other, + format!("Could not write to test file: {}", e), + )); + }; + + if let Err(e) = lint_file.write_all( + get_lint_file_contents( + pass_type, + pass_lifetimes, + lint_name, + &camel_case_name, + category, + pass_import, + context_import, + ) + .as_bytes(), + ) { + return Err(io::Error::new( + ErrorKind::Other, + format!("Could not write to lint file: {}", e), + )); + } + Ok(()) + }, + Err(e) => Err(io::Error::new( + ErrorKind::Other, + format!("Unable to create lint: {}", e), + )), + } +} + +fn open_files(lint_name: &str) -> Result<(File, File), io::Error> { + let project_root = clippy_project_root(); + + let test_file_path = project_root.join("tests").join("ui").join(format!("{}.rs", lint_name)); + let lint_file_path = project_root + .join("clippy_lints") + .join("src") + .join(format!("{}.rs", lint_name)); + + if Path::new(&test_file_path).exists() { + return Err(io::Error::new( + ErrorKind::AlreadyExists, + format!("test file {:?} already exists", test_file_path), + )); + } + if Path::new(&lint_file_path).exists() { + return Err(io::Error::new( + ErrorKind::AlreadyExists, + format!("lint file {:?} already exists", lint_file_path), + )); + } + + let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?; + let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?; + + Ok((test_file, lint_file)) +} + +fn to_camel_case(name: &str) -> String { + name.split('_') + .map(|s| { + if s.is_empty() { + String::from("") + } else { + [&s[0..1].to_uppercase(), &s[1..]].concat() + } + }) + .collect() +} + +fn get_test_file_contents(lint_name: &str) -> String { + format!( + "#![warn(clippy::{})] + +fn main() {{ + // test code goes here +}} +", + lint_name + ) +} + +fn get_lint_file_contents( + pass_type: &str, + pass_lifetimes: &str, + lint_name: &str, + camel_case_name: &str, + category: &str, + pass_import: &str, + context_import: &str, +) -> String { + format!( + "use rustc_lint::{{{type}, {context_import}}}; +use rustc_session::{{declare_lint_pass, declare_tool_lint}}; +{pass_import} + +declare_clippy_lint! {{ + /// **What it does:** + /// + /// **Why is this bad?** + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// ``` + pub {name_upper}, + {category}, + \"default lint description\" +}} + +declare_lint_pass!({name_camel} => [{name_upper}]); + +impl {type}{lifetimes} for {name_camel} {{}} +", + type=pass_type, + lifetimes=pass_lifetimes, + name_upper=lint_name.to_uppercase(), + name_camel=camel_case_name, + category=category, + pass_import=pass_import, + context_import=context_import + ) +} + +#[test] +fn test_camel_case() { + let s = "a_lint"; + let s2 = to_camel_case(s); + assert_eq!(s2, "ALint"); + + let name = "a_really_long_new_lint"; + let name2 = to_camel_case(name); + assert_eq!(name2, "AReallyLongNewLint"); + + let name3 = "lint__name"; + let name4 = to_camel_case(name3); + assert_eq!(name4, "LintName"); +} diff --git a/src/tools/clippy/clippy_dev/src/stderr_length_check.rs b/src/tools/clippy/clippy_dev/src/stderr_length_check.rs new file mode 100644 index 0000000000000..e02b6f7da5f7b --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/stderr_length_check.rs @@ -0,0 +1,51 @@ +use crate::clippy_project_root; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +// The maximum length allowed for stderr files. +// +// We limit this because small files are easier to deal with than bigger files. +const LENGTH_LIMIT: usize = 200; + +pub fn check() { + let exceeding_files: Vec<_> = exceeding_stderr_files(); + + if !exceeding_files.is_empty() { + eprintln!("Error: stderr files exceeding limit of {} lines:", LENGTH_LIMIT); + for (path, count) in exceeding_files { + println!("{}: {}", path.display(), count); + } + std::process::exit(1); + } +} + +fn exceeding_stderr_files() -> Vec<(PathBuf, usize)> { + // We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories. + WalkDir::new(clippy_project_root().join("tests/ui")) + .into_iter() + .filter_map(Result::ok) + .filter(|f| !f.file_type().is_dir()) + .filter_map(|e| { + let p = e.into_path(); + let count = count_linenumbers(&p); + if p.extension() == Some(OsStr::new("stderr")) && count > LENGTH_LIMIT { + Some((p, count)) + } else { + None + } + }) + .collect() +} + +#[must_use] +fn count_linenumbers(filepath: &Path) -> usize { + match fs::read(filepath) { + Ok(content) => bytecount::count(&content, b'\n'), + Err(e) => { + eprintln!("Failed to read file: {}", e); + 0 + }, + } +} diff --git a/src/tools/clippy/clippy_dev/src/update_lints.rs b/src/tools/clippy/clippy_dev/src/update_lints.rs new file mode 100644 index 0000000000000..a9a7092994269 --- /dev/null +++ b/src/tools/clippy/clippy_dev/src/update_lints.rs @@ -0,0 +1,162 @@ +use crate::{ + gather_all, gen_changelog_lint_list, gen_deprecated, gen_lint_group_list, gen_modules_list, gen_register_lint_list, + replace_region_in_file, Lint, DOCS_LINK, +}; +use std::path::Path; + +#[derive(Clone, Copy, PartialEq)] +pub enum UpdateMode { + Check, + Change, +} + +#[allow(clippy::too_many_lines)] +pub fn run(update_mode: UpdateMode) { + let lint_list: Vec = gather_all().collect(); + + let internal_lints = Lint::internal_lints(&lint_list); + let deprecated_lints = Lint::deprecated_lints(&lint_list); + let usable_lints = Lint::usable_lints(&lint_list); + let mut sorted_usable_lints = usable_lints.clone(); + sorted_usable_lints.sort_by_key(|lint| lint.name.clone()); + + let usable_lint_count = round_to_fifty(usable_lints.len()); + + let mut file_change = replace_region_in_file( + Path::new("src/lintlist/mod.rs"), + "begin lint list", + "end lint list", + false, + update_mode == UpdateMode::Change, + || { + format!("pub static ref ALL_LINTS: Vec = vec!{:#?};", sorted_usable_lints) + .lines() + .map(ToString::to_string) + .collect::>() + }, + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("README.md"), + &format!( + r#"\[There are over \d+ lints included in this crate!\]\({}\)"#, + DOCS_LINK + ), + "", + true, + update_mode == UpdateMode::Change, + || { + vec![format!( + "[There are over {} lints included in this crate!]({})", + usable_lint_count, DOCS_LINK + )] + }, + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("CHANGELOG.md"), + "", + "", + false, + update_mode == UpdateMode::Change, + || gen_changelog_lint_list(usable_lints.iter().chain(deprecated_lints.iter())), + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + "begin deprecated lints", + "end deprecated lints", + false, + update_mode == UpdateMode::Change, + || gen_deprecated(deprecated_lints.iter()), + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + "begin register lints", + "end register lints", + false, + update_mode == UpdateMode::Change, + || gen_register_lint_list(usable_lints.iter().chain(internal_lints.iter())), + ) + .changed; + + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + "begin lints modules", + "end lints modules", + false, + update_mode == UpdateMode::Change, + || gen_modules_list(usable_lints.iter()), + ) + .changed; + + // Generate lists of lints in the clippy::all lint group + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + r#"store.register_group\(true, "clippy::all""#, + r#"\]\);"#, + false, + update_mode == UpdateMode::Change, + || { + // clippy::all should only include the following lint groups: + let all_group_lints = usable_lints.iter().filter(|l| { + l.group == "correctness" || l.group == "style" || l.group == "complexity" || l.group == "perf" + }); + + gen_lint_group_list(all_group_lints) + }, + ) + .changed; + + // Generate the list of lints for all other lint groups + for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) { + file_change |= replace_region_in_file( + Path::new("clippy_lints/src/lib.rs"), + &format!("store.register_group\\(true, \"clippy::{}\"", lint_group), + r#"\]\);"#, + false, + update_mode == UpdateMode::Change, + || gen_lint_group_list(lints.iter()), + ) + .changed; + } + + if update_mode == UpdateMode::Check && file_change { + println!( + "Not all lints defined properly. \ + Please run `cargo dev update_lints` to make sure all lints are defined properly." + ); + std::process::exit(1); + } +} + +pub fn print_lints() { + let lint_list: Vec = gather_all().collect(); + let usable_lints = Lint::usable_lints(&lint_list); + let usable_lint_count = usable_lints.len(); + let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter()); + + for (lint_group, mut lints) in grouped_by_lint_group { + if lint_group == "Deprecated" { + continue; + } + println!("\n## {}", lint_group); + + lints.sort_by_key(|l| l.name.clone()); + + for lint in lints { + println!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc); + } + } + + println!("there are {} lints", usable_lint_count); +} + +fn round_to_fifty(count: usize) -> usize { + count / 50 * 50 +} diff --git a/src/tools/clippy/clippy_dummy/Cargo.toml b/src/tools/clippy/clippy_dummy/Cargo.toml new file mode 100644 index 0000000000000..7b11795fafdc5 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "clippy_dummy" # rename to clippy before publishing +version = "0.0.303" +authors = ["Manish Goregaokar "] +edition = "2018" +readme = "crates-readme.md" +description = "A bunch of helpful lints to avoid common pitfalls in Rust." +build = 'build.rs' + +repository = "https://github.com/rust-lang/rust-clippy" + +license = "MIT OR Apache-2.0" +keywords = ["clippy", "lint", "plugin"] +categories = ["development-tools", "development-tools::cargo-plugins"] + +[build-dependencies] +term = "0.6" diff --git a/src/tools/clippy/clippy_dummy/PUBLISH.md b/src/tools/clippy/clippy_dummy/PUBLISH.md new file mode 100644 index 0000000000000..8e420ec959a26 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/PUBLISH.md @@ -0,0 +1,6 @@ +This is a dummy crate to publish to crates.io. It primarily exists to ensure +that folks trying to install clippy from crates.io get redirected to the +`rustup` technique. + +Before publishing, be sure to rename `clippy_dummy` to `clippy` in `Cargo.toml`, +it has a different name to avoid workspace issues. diff --git a/src/tools/clippy/clippy_dummy/build.rs b/src/tools/clippy/clippy_dummy/build.rs new file mode 100644 index 0000000000000..21af4f8244f44 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/build.rs @@ -0,0 +1,42 @@ +use term::color::{GREEN, RED, WHITE}; +use term::{Attr, Error, Result}; + +fn main() { + if foo().is_err() { + eprintln!( + "error: Clippy is no longer available via crates.io\n\n\ + help: please run `rustup component add clippy` instead" + ); + } + std::process::exit(1); +} + +fn foo() -> Result<()> { + let mut t = term::stderr().ok_or(Error::NotSupported)?; + + t.attr(Attr::Bold)?; + t.fg(RED)?; + write!(t, "\nerror: ")?; + + t.reset()?; + t.fg(WHITE)?; + writeln!(t, "Clippy is no longer available via crates.io\n")?; + + t.attr(Attr::Bold)?; + t.fg(GREEN)?; + write!(t, "help: ")?; + + t.reset()?; + t.fg(WHITE)?; + write!(t, "please run `")?; + + t.attr(Attr::Bold)?; + write!(t, "rustup component add clippy")?; + + t.reset()?; + t.fg(WHITE)?; + writeln!(t, "` instead")?; + + t.reset()?; + Ok(()) +} diff --git a/src/tools/clippy/clippy_dummy/crates-readme.md b/src/tools/clippy/clippy_dummy/crates-readme.md new file mode 100644 index 0000000000000..0decae8b9103d --- /dev/null +++ b/src/tools/clippy/clippy_dummy/crates-readme.md @@ -0,0 +1,9 @@ +Installing clippy via crates.io is deprecated. Please use the following: + +```terminal +rustup component add clippy +``` + +on a Rust version 1.29 or later. You may need to run `rustup self update` if it complains about a missing clippy binary. + +See [the homepage](https://github.com/rust-lang/rust-clippy/#clippy) for more information diff --git a/src/tools/clippy/clippy_dummy/src/main.rs b/src/tools/clippy/clippy_dummy/src/main.rs new file mode 100644 index 0000000000000..a118834f1fd47 --- /dev/null +++ b/src/tools/clippy/clippy_dummy/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + panic!("This shouldn't even compile") +} diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml new file mode 100644 index 0000000000000..1c0be72783462 --- /dev/null +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "clippy_lints" +# begin automatic update +version = "0.0.212" +# end automatic update +authors = [ + "Manish Goregaokar ", + "Andre Bogus ", + "Georg Brandl ", + "Martin Carton " +] +description = "A bunch of helpful lints to avoid common pitfalls in Rust" +repository = "https://github.com/rust-lang/rust-clippy" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["clippy", "lint", "plugin"] +edition = "2018" + +[dependencies] +cargo_metadata = "0.9.0" +if_chain = "1.0.0" +itertools = "0.9" +lazy_static = "1.0.2" +pulldown-cmark = { version = "0.7", default-features = false } +quine-mc_cluskey = "0.2.2" +regex-syntax = "0.6" +serde = { version = "1.0", features = ["derive"] } +smallvec = { version = "1", features = ["union"] } +toml = "0.5.3" +unicode-normalization = "0.1" +semver = "0.9.0" +# NOTE: cargo requires serde feat in its url dep +# see +url = { version = "2.1.0", features = ["serde"] } + +[features] +deny-warnings = [] diff --git a/src/tools/clippy/clippy_lints/README.md b/src/tools/clippy/clippy_lints/README.md new file mode 100644 index 0000000000000..513583b7e349e --- /dev/null +++ b/src/tools/clippy/clippy_lints/README.md @@ -0,0 +1 @@ +This crate contains Clippy lints. For the main crate, check [GitHub](https://github.com/rust-lang/rust-clippy). diff --git a/src/tools/clippy/clippy_lints/src/approx_const.rs b/src/tools/clippy/clippy_lints/src/approx_const.rs new file mode 100644 index 0000000000000..7e6e2c7eaebea --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/approx_const.rs @@ -0,0 +1,117 @@ +use crate::utils::span_lint; +use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol; +use std::f64::consts as f64; + +declare_clippy_lint! { + /// **What it does:** Checks for floating point literals that approximate + /// constants which are defined in + /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants) + /// or + /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants), + /// respectively, suggesting to use the predefined constant. + /// + /// **Why is this bad?** Usually, the definition in the standard library is more + /// precise than what people come up with. If you find that your definition is + /// actually more precise, please [file a Rust + /// issue](https://github.com/rust-lang/rust/issues). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 3.14; + /// let y = 1_f64 / x; + /// ``` + /// Use predefined constants instead: + /// ```rust + /// let x = std::f32::consts::PI; + /// let y = std::f64::consts::FRAC_1_PI; + /// ``` + pub APPROX_CONSTANT, + correctness, + "the approximate of a known float constant (in `std::fXX::consts`)" +} + +// Tuples are of the form (constant, name, min_digits) +const KNOWN_CONSTS: [(f64, &str, usize); 18] = [ + (f64::E, "E", 4), + (f64::FRAC_1_PI, "FRAC_1_PI", 4), + (f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5), + (f64::FRAC_2_PI, "FRAC_2_PI", 5), + (f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5), + (f64::FRAC_PI_2, "FRAC_PI_2", 5), + (f64::FRAC_PI_3, "FRAC_PI_3", 5), + (f64::FRAC_PI_4, "FRAC_PI_4", 5), + (f64::FRAC_PI_6, "FRAC_PI_6", 5), + (f64::FRAC_PI_8, "FRAC_PI_8", 5), + (f64::LN_10, "LN_10", 5), + (f64::LN_2, "LN_2", 5), + (f64::LOG10_E, "LOG10_E", 5), + (f64::LOG2_E, "LOG2_E", 5), + (f64::LOG2_10, "LOG2_10", 5), + (f64::LOG10_2, "LOG10_2", 5), + (f64::PI, "PI", 3), + (f64::SQRT_2, "SQRT_2", 5), +]; + +declare_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ApproxConstant { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Lit(lit) = &e.kind { + check_lit(cx, &lit.node, e); + } + } +} + +fn check_lit(cx: &LateContext<'_, '_>, lit: &LitKind, e: &Expr<'_>) { + match *lit { + LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty { + FloatTy::F32 => check_known_consts(cx, e, s, "f32"), + FloatTy::F64 => check_known_consts(cx, e, s, "f64"), + }, + LitKind::Float(s, LitFloatType::Unsuffixed) => check_known_consts(cx, e, s, "f{32, 64}"), + _ => (), + } +} + +fn check_known_consts(cx: &LateContext<'_, '_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) { + let s = s.as_str(); + if s.parse::().is_ok() { + for &(constant, name, min_digits) in &KNOWN_CONSTS { + if is_approx_const(constant, &s, min_digits) { + span_lint( + cx, + APPROX_CONSTANT, + e.span, + &format!( + "approximate value of `{}::consts::{}` found. \ + Consider using it directly", + module, &name + ), + ); + return; + } + } + } +} + +/// Returns `false` if the number of significant figures in `value` are +/// less than `min_digits`; otherwise, returns true if `value` is equal +/// to `constant`, rounded to the number of digits present in `value`. +#[must_use] +fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool { + if value.len() <= min_digits { + false + } else if constant.to_string().starts_with(value) { + // The value is a truncated constant + true + } else { + let round_const = format!("{:.*}", value.len() - 2, constant); + value == round_const + } +} diff --git a/src/tools/clippy/clippy_lints/src/arithmetic.rs b/src/tools/clippy/clippy_lints/src/arithmetic.rs new file mode 100644 index 0000000000000..6cbe10a5352d1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/arithmetic.rs @@ -0,0 +1,149 @@ +use crate::consts::constant_simple; +use crate::utils::span_lint; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for integer arithmetic operations which could overflow or panic. + /// + /// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable + /// of overflowing according to the [Rust + /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow), + /// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is + /// attempted. + /// + /// **Why is this bad?** Integer overflow will trigger a panic in debug builds or will wrap in + /// release mode. Division by zero will cause a panic in either mode. In some applications one + /// wants explicitly checked, wrapping or saturating arithmetic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = 0; + /// a + 1; + /// ``` + pub INTEGER_ARITHMETIC, + restriction, + "any integer arithmetic expression which could overflow or panic" +} + +declare_clippy_lint! { + /// **What it does:** Checks for float arithmetic. + /// + /// **Why is this bad?** For some embedded systems or kernel development, it + /// can be useful to rule out floating-point numbers. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = 0.0; + /// a + 1.0; + /// ``` + pub FLOAT_ARITHMETIC, + restriction, + "any floating-point arithmetic statement" +} + +#[derive(Copy, Clone, Default)] +pub struct Arithmetic { + expr_span: Option, + /// This field is used to check whether expressions are constants, such as in enum discriminants + /// and consts + const_span: Option, +} + +impl_lint_pass!(Arithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Arithmetic { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if self.expr_span.is_some() { + return; + } + + if let Some(span) = self.const_span { + if span.contains(expr.span) { + return; + } + } + match &expr.kind { + hir::ExprKind::Binary(op, l, r) | hir::ExprKind::AssignOp(op, l, r) => { + match op.node { + hir::BinOpKind::And + | hir::BinOpKind::Or + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr + | hir::BinOpKind::BitXor + | hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => return, + _ => (), + } + + let (l_ty, r_ty) = (cx.tables.expr_ty(l), cx.tables.expr_ty(r)); + if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_span = Some(expr.span); + } else if l_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() { + span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); + self.expr_span = Some(expr.span); + } + }, + hir::ExprKind::Unary(hir::UnOp::UnNeg, arg) => { + let ty = cx.tables.expr_ty(arg); + if constant_simple(cx, cx.tables, expr).is_none() { + if ty.is_integral() { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_span = Some(expr.span); + } else if ty.is_floating_point() { + span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); + self.expr_span = Some(expr.span); + } + } + }, + _ => (), + } + } + + fn check_expr_post(&mut self, _: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if Some(expr.span) == self.expr_span { + self.expr_span = None; + } + } + + fn check_body(&mut self, cx: &LateContext<'_, '_>, body: &hir::Body<'_>) { + let body_owner = cx.tcx.hir().body_owner(body.id()); + + match cx.tcx.hir().body_owner_kind(body_owner) { + hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => { + let body_span = cx.tcx.hir().span(body_owner); + + if let Some(span) = self.const_span { + if span.contains(body_span) { + return; + } + } + self.const_span = Some(body_span); + }, + hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (), + } + } + + fn check_body_post(&mut self, cx: &LateContext<'_, '_>, body: &hir::Body<'_>) { + let body_owner = cx.tcx.hir().body_owner(body.id()); + let body_span = cx.tcx.hir().span(body_owner); + + if let Some(span) = self.const_span { + if span.contains(body_span) { + return; + } + } + self.const_span = None; + } +} diff --git a/src/tools/clippy/clippy_lints/src/as_conversions.rs b/src/tools/clippy/clippy_lints/src/as_conversions.rs new file mode 100644 index 0000000000000..0c8efd755146e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/as_conversions.rs @@ -0,0 +1,58 @@ +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `as` conversions. + /// + /// **Why is this bad?** `as` conversions will perform many kinds of + /// conversions, including silently lossy conversions and dangerous coercions. + /// There are cases when it makes sense to use `as`, so the lint is + /// Allow by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let a: u32; + /// ... + /// f(a as u16); + /// ``` + /// + /// Usually better represents the semantics you expect: + /// ```rust,ignore + /// f(a.try_into()?); + /// ``` + /// or + /// ```rust,ignore + /// f(a.try_into().expect("Unexpected u16 overflow in f")); + /// ``` + /// + pub AS_CONVERSIONS, + restriction, + "using a potentially dangerous silent `as` conversion" +} + +declare_lint_pass!(AsConversions => [AS_CONVERSIONS]); + +impl EarlyLintPass for AsConversions { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Cast(_, _) = expr.kind { + span_lint_and_help( + cx, + AS_CONVERSIONS, + expr.span, + "using a potentially dangerous silent `as` conversion", + None, + "consider using a safe wrapper for this conversion", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/assertions_on_constants.rs b/src/tools/clippy/clippy_lints/src/assertions_on_constants.rs new file mode 100644 index 0000000000000..f8a8fdcd3aa35 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/assertions_on_constants.rs @@ -0,0 +1,152 @@ +use crate::consts::{constant, Constant}; +use crate::utils::paths; +use crate::utils::{is_direct_expn_of, is_expn_of, match_function_call, snippet_opt, span_lint_and_help}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind, PatKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `assert!(true)` and `assert!(false)` calls. + /// + /// **Why is this bad?** Will be optimized out by the compiler or should probably be replaced by a + /// `panic!()` or `unreachable!()` + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust,ignore + /// assert!(false) + /// assert!(true) + /// const B: bool = false; + /// assert!(B) + /// ``` + pub ASSERTIONS_ON_CONSTANTS, + style, + "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`" +} + +declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssertionsOnConstants { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + let lint_true = |is_debug: bool| { + span_lint_and_help( + cx, + ASSERTIONS_ON_CONSTANTS, + e.span, + if is_debug { + "`debug_assert!(true)` will be optimized out by the compiler" + } else { + "`assert!(true)` will be optimized out by the compiler" + }, + None, + "remove it", + ); + }; + let lint_false_without_message = || { + span_lint_and_help( + cx, + ASSERTIONS_ON_CONSTANTS, + e.span, + "`assert!(false)` should probably be replaced", + None, + "use `panic!()` or `unreachable!()`", + ); + }; + let lint_false_with_message = |panic_message: String| { + span_lint_and_help( + cx, + ASSERTIONS_ON_CONSTANTS, + e.span, + &format!("`assert!(false, {})` should probably be replaced", panic_message), + None, + &format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message), + ) + }; + + if let Some(debug_assert_span) = is_expn_of(e.span, "debug_assert") { + if debug_assert_span.from_expansion() { + return; + } + if_chain! { + if let ExprKind::Unary(_, ref lit) = e.kind; + if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.tables, lit); + if is_true; + then { + lint_true(true); + } + }; + } else if let Some(assert_span) = is_direct_expn_of(e.span, "assert") { + if assert_span.from_expansion() { + return; + } + if let Some(assert_match) = match_assert_with_message(&cx, e) { + match assert_match { + // matched assert but not message + AssertKind::WithoutMessage(false) => lint_false_without_message(), + AssertKind::WithoutMessage(true) | AssertKind::WithMessage(_, true) => lint_true(false), + AssertKind::WithMessage(panic_message, false) => lint_false_with_message(panic_message), + }; + } + } + } +} + +/// Result of calling `match_assert_with_message`. +enum AssertKind { + WithMessage(String, bool), + WithoutMessage(bool), +} + +/// Check if the expression matches +/// +/// ```rust,ignore +/// match { let _t = !c; _t } { +/// true => { +/// { +/// ::std::rt::begin_panic(message, _) +/// } +/// } +/// _ => { } +/// }; +/// ``` +/// +/// where `message` is any expression and `c` is a constant bool. +fn match_assert_with_message<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> Option { + if_chain! { + if let ExprKind::Match(ref expr, ref arms, _) = expr.kind; + // matches { let _t = expr; _t } + if let ExprKind::DropTemps(ref expr) = expr.kind; + if let ExprKind::Unary(UnOp::UnNot, ref expr) = expr.kind; + // bind the first argument of the `assert!` macro + if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.tables, expr); + // arm 1 pattern + if let PatKind::Lit(ref lit_expr) = arms[0].pat.kind; + if let ExprKind::Lit(ref lit) = lit_expr.kind; + if let LitKind::Bool(true) = lit.node; + // arm 1 block + if let ExprKind::Block(ref block, _) = arms[0].body.kind; + if block.stmts.is_empty(); + if let Some(block_expr) = &block.expr; + if let ExprKind::Block(ref inner_block, _) = block_expr.kind; + if let Some(begin_panic_call) = &inner_block.expr; + // function call + if let Some(args) = match_function_call(cx, begin_panic_call, &paths::BEGIN_PANIC); + if args.len() == 1; + // bind the second argument of the `assert!` macro if it exists + if let panic_message = snippet_opt(cx, args[0].span); + // second argument of begin_panic is irrelevant + // as is the second match arm + then { + // an empty message occurs when it was generated by the macro + // (and not passed by the user) + return panic_message + .filter(|msg| !msg.is_empty()) + .map(|msg| AssertKind::WithMessage(msg, is_true)) + .or(Some(AssertKind::WithoutMessage(is_true))); + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/assign_ops.rs b/src/tools/clippy/clippy_lints/src/assign_ops.rs new file mode 100644 index 0000000000000..05e2650d0b715 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/assign_ops.rs @@ -0,0 +1,261 @@ +use crate::utils::{ + get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method, SpanlessEq, +}; +use crate::utils::{higher, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `a = a op b` or `a = b commutative_op a` + /// patterns. + /// + /// **Why is this bad?** These can be written as the shorter `a op= b`. + /// + /// **Known problems:** While forbidden by the spec, `OpAssign` traits may have + /// implementations that differ from the regular `Op` impl. + /// + /// **Example:** + /// ```rust + /// let mut a = 5; + /// let b = 0; + /// // ... + /// a = a + b; + /// ``` + pub ASSIGN_OP_PATTERN, + style, + "assigning the result of an operation on a variable to that same variable" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `a op= a op b` or `a op= b op a` patterns. + /// + /// **Why is this bad?** Most likely these are bugs where one meant to write `a + /// op= b`. + /// + /// **Known problems:** Clippy cannot know for sure if `a op= a op b` should have + /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both. + /// If `a op= a op b` is really the correct behaviour it should be + /// written as `a = a op a op b` as it's less confusing. + /// + /// **Example:** + /// ```rust + /// let mut a = 5; + /// let b = 2; + /// // ... + /// a += a + b; + /// ``` + pub MISREFACTORED_ASSIGN_OP, + complexity, + "having a variable on both sides of an assign op" +} + +declare_lint_pass!(AssignOps => [ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssignOps { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + match &expr.kind { + hir::ExprKind::AssignOp(op, lhs, rhs) => { + if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind { + if op.node != binop.node { + return; + } + // lhs op= l op r + if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, l) { + lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r); + } + // lhs op= l commutative_op r + if is_commutative(op.node) && SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, r) { + lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l); + } + } + }, + hir::ExprKind::Assign(assignee, e, _) => { + if let hir::ExprKind::Binary(op, l, r) = &e.kind { + let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| { + let ty = cx.tables.expr_ty(assignee); + let rty = cx.tables.expr_ty(rhs); + macro_rules! ops { + ($op:expr, + $cx:expr, + $ty:expr, + $rty:expr, + $($trait_name:ident),+) => { + match $op { + $(hir::BinOpKind::$trait_name => { + let [krate, module] = crate::utils::paths::OPS_MODULE; + let path: [&str; 3] = [krate, module, concat!(stringify!($trait_name), "Assign")]; + let trait_id = if let Some(trait_id) = get_trait_def_id($cx, &path) { + trait_id + } else { + return; // useless if the trait doesn't exist + }; + // check that we are not inside an `impl AssignOp` of this exact operation + let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id); + if_chain! { + if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn); + if trait_ref.path.res.def_id() == trait_id; + then { return; } + } + implements_trait($cx, $ty, trait_id, &[$rty]) + },)* + _ => false, + } + } + } + if ops!( + op.node, + cx, + ty, + rty.into(), + Add, + Sub, + Mul, + Div, + Rem, + And, + Or, + BitAnd, + BitOr, + BitXor, + Shr, + Shl + ) { + span_lint_and_then( + cx, + ASSIGN_OP_PATTERN, + expr.span, + "manual implementation of an assign operation", + |diag| { + if let (Some(snip_a), Some(snip_r)) = + (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span)) + { + diag.span_suggestion( + expr.span, + "replace it with", + format!("{} {}= {}", snip_a, op.node.as_str(), snip_r), + Applicability::MachineApplicable, + ); + } + }, + ); + } + }; + + let mut visitor = ExprVisitor { + assignee, + counter: 0, + cx, + }; + + walk_expr(&mut visitor, e); + + if visitor.counter == 1 { + // a = a op b + if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, l) { + lint(assignee, r); + } + // a = b commutative_op a + // Limited to primitive type as these ops are know to be commutative + if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, r) + && cx.tables.expr_ty(assignee).is_primitive_ty() + { + match op.node { + hir::BinOpKind::Add + | hir::BinOpKind::Mul + | hir::BinOpKind::And + | hir::BinOpKind::Or + | hir::BinOpKind::BitXor + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr => { + lint(assignee, l); + }, + _ => {}, + } + } + } + } + }, + _ => {}, + } + } +} + +fn lint_misrefactored_assign_op( + cx: &LateContext<'_, '_>, + expr: &hir::Expr<'_>, + op: hir::BinOp, + rhs: &hir::Expr<'_>, + assignee: &hir::Expr<'_>, + rhs_other: &hir::Expr<'_>, +) { + span_lint_and_then( + cx, + MISREFACTORED_ASSIGN_OP, + expr.span, + "variable appears on both sides of an assignment operation", + |diag| { + if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) { + let a = &sugg::Sugg::hir(cx, assignee, ".."); + let r = &sugg::Sugg::hir(cx, rhs, ".."); + let long = format!("{} = {}", snip_a, sugg::make_binop(higher::binop(op.node), a, r)); + diag.span_suggestion( + expr.span, + &format!( + "Did you mean `{} = {} {} {}` or `{}`? Consider replacing it with", + snip_a, + snip_a, + op.node.as_str(), + snip_r, + long + ), + format!("{} {}= {}", snip_a, op.node.as_str(), snip_r), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + expr.span, + "or", + long, + Applicability::MaybeIncorrect, // snippet + ); + } + }, + ); +} + +#[must_use] +fn is_commutative(op: hir::BinOpKind) -> bool { + use rustc_hir::BinOpKind::{ + Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub, + }; + match op { + Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true, + Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false, + } +} + +struct ExprVisitor<'a, 'tcx> { + assignee: &'a hir::Expr<'a>, + counter: u8, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if SpanlessEq::new(self.cx).ignore_fn().eq_expr(self.assignee, expr) { + self.counter += 1; + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/atomic_ordering.rs b/src/tools/clippy/clippy_lints/src/atomic_ordering.rs new file mode 100644 index 0000000000000..73b4cef472505 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/atomic_ordering.rs @@ -0,0 +1,135 @@ +use crate::utils::{match_def_path, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::def_id::DefId; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of invalid atomic + /// ordering in atomic loads/stores and memory fences. + /// + /// **Why is this bad?** Using an invalid atomic ordering + /// will cause a panic at run-time. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,no_run + /// # use std::sync::atomic::{self, AtomicBool, Ordering}; + /// + /// let x = AtomicBool::new(true); + /// + /// let _ = x.load(Ordering::Release); + /// let _ = x.load(Ordering::AcqRel); + /// + /// x.store(false, Ordering::Acquire); + /// x.store(false, Ordering::AcqRel); + /// + /// atomic::fence(Ordering::Relaxed); + /// atomic::compiler_fence(Ordering::Relaxed); + /// ``` + pub INVALID_ATOMIC_ORDERING, + correctness, + "usage of invalid atomic ordering in atomic loads/stores and memory fences" +} + +declare_lint_pass!(AtomicOrdering => [INVALID_ATOMIC_ORDERING]); + +const ATOMIC_TYPES: [&str; 12] = [ + "AtomicBool", + "AtomicI8", + "AtomicI16", + "AtomicI32", + "AtomicI64", + "AtomicIsize", + "AtomicPtr", + "AtomicU8", + "AtomicU16", + "AtomicU32", + "AtomicU64", + "AtomicUsize", +]; + +fn type_is_atomic(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + if let ty::Adt(&ty::AdtDef { did, .. }, _) = cx.tables.expr_ty(expr).kind { + ATOMIC_TYPES + .iter() + .any(|ty| match_def_path(cx, did, &["core", "sync", "atomic", ty])) + } else { + false + } +} + +fn match_ordering_def_path(cx: &LateContext<'_, '_>, did: DefId, orderings: &[&str]) -> bool { + orderings + .iter() + .any(|ordering| match_def_path(cx, did, &["core", "sync", "atomic", "Ordering", ordering])) +} + +fn check_atomic_load_store(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(ref method_path, _, args) = &expr.kind; + let method = method_path.ident.name.as_str(); + if type_is_atomic(cx, &args[0]); + if method == "load" || method == "store"; + let ordering_arg = if method == "load" { &args[1] } else { &args[2] }; + if let ExprKind::Path(ref ordering_qpath) = ordering_arg.kind; + if let Some(ordering_def_id) = cx.tables.qpath_res(ordering_qpath, ordering_arg.hir_id).opt_def_id(); + then { + if method == "load" && + match_ordering_def_path(cx, ordering_def_id, &["Release", "AcqRel"]) { + span_lint_and_help( + cx, + INVALID_ATOMIC_ORDERING, + ordering_arg.span, + "atomic loads cannot have `Release` and `AcqRel` ordering", + None, + "consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`" + ); + } else if method == "store" && + match_ordering_def_path(cx, ordering_def_id, &["Acquire", "AcqRel"]) { + span_lint_and_help( + cx, + INVALID_ATOMIC_ORDERING, + ordering_arg.span, + "atomic stores cannot have `Acquire` and `AcqRel` ordering", + None, + "consider using ordering modes `Release`, `SeqCst` or `Relaxed`" + ); + } + } + } +} + +fn check_memory_fence(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref func, ref args) = expr.kind; + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.tables.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if ["fence", "compiler_fence"] + .iter() + .any(|func| match_def_path(cx, def_id, &["core", "sync", "atomic", func])); + if let ExprKind::Path(ref ordering_qpath) = &args[0].kind; + if let Some(ordering_def_id) = cx.tables.qpath_res(ordering_qpath, args[0].hir_id).opt_def_id(); + if match_ordering_def_path(cx, ordering_def_id, &["Relaxed"]); + then { + span_lint_and_help( + cx, + INVALID_ATOMIC_ORDERING, + args[0].span, + "memory fences cannot have `Relaxed` ordering", + None, + "consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`" + ); + } + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AtomicOrdering { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + check_atomic_load_store(cx, expr); + check_memory_fence(cx, expr); + } +} diff --git a/src/tools/clippy/clippy_lints/src/attrs.rs b/src/tools/clippy/clippy_lints/src/attrs.rs new file mode 100644 index 0000000000000..64abc9fdc7174 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/attrs.rs @@ -0,0 +1,657 @@ +//! checks for attributes + +use crate::reexport::Name; +use crate::utils::{ + first_line_of_span, is_present_in_source, match_def_path, paths, snippet_opt, span_lint, span_lint_and_sugg, + span_lint_and_then, without_block_comments, +}; +use if_chain::if_chain; +use rustc_ast::ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem}; +use rustc_ast::util::lev_distance::find_best_match_for_name; +use rustc_errors::Applicability; +use rustc_hir::{ + Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, +}; +use rustc_lint::{CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; +use semver::Version; + +static UNIX_SYSTEMS: &[&str] = &[ + "android", + "dragonfly", + "emscripten", + "freebsd", + "fuchsia", + "haiku", + "illumos", + "ios", + "l4re", + "linux", + "macos", + "netbsd", + "openbsd", + "redox", + "solaris", + "vxworks", +]; + +// NOTE: windows is excluded from the list because it's also a valid target family. +static NON_UNIX_SYSTEMS: &[&str] = &["cloudabi", "hermit", "none", "wasi"]; + +declare_clippy_lint! { + /// **What it does:** Checks for items annotated with `#[inline(always)]`, + /// unless the annotated function is empty or simply panics. + /// + /// **Why is this bad?** While there are valid uses of this annotation (and once + /// you know when to use it, by all means `allow` this lint), it's a common + /// newbie-mistake to pepper one's code with it. + /// + /// As a rule of thumb, before slapping `#[inline(always)]` on a function, + /// measure if that additional function call really affects your runtime profile + /// sufficiently to make up for the increase in compile time. + /// + /// **Known problems:** False positives, big time. This lint is meant to be + /// deactivated by everyone doing serious performance work. This means having + /// done the measurement. + /// + /// **Example:** + /// ```ignore + /// #[inline(always)] + /// fn not_quite_hot_code(..) { ... } + /// ``` + pub INLINE_ALWAYS, + pedantic, + "use of `#[inline(always)]`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `extern crate` and `use` items annotated with + /// lint attributes. + /// + /// This lint whitelists `#[allow(unused_imports)]`, `#[allow(deprecated)]` and + /// `#[allow(unreachable_pub)]` on `use` items and `#[allow(unused_imports)]` on + /// `extern crate` items with a `#[macro_use]` attribute. + /// + /// **Why is this bad?** Lint attributes have no effect on crate imports. Most + /// likely a `!` was forgotten. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// // Bad + /// #[deny(dead_code)] + /// extern crate foo; + /// #[forbid(dead_code)] + /// use foo::bar; + /// + /// // Ok + /// #[allow(unused_imports)] + /// use foo::baz; + /// #[allow(unused_imports)] + /// #[macro_use] + /// extern crate baz; + /// ``` + pub USELESS_ATTRIBUTE, + correctness, + "use of lint attributes on `extern crate` items" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `#[deprecated]` annotations with a `since` + /// field that is not a valid semantic version. + /// + /// **Why is this bad?** For checking the version of the deprecation, it must be + /// a valid semver. Failing that, the contained information is useless. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// #[deprecated(since = "forever")] + /// fn something_else() { /* ... */ } + /// ``` + pub DEPRECATED_SEMVER, + correctness, + "use of `#[deprecated(since = \"x\")]` where x is not semver" +} + +declare_clippy_lint! { + /// **What it does:** Checks for empty lines after outer attributes + /// + /// **Why is this bad?** + /// Most likely the attribute was meant to be an inner attribute using a '!'. + /// If it was meant to be an outer attribute, then the following item + /// should not be separated by empty lines. + /// + /// **Known problems:** Can cause false positives. + /// + /// From the clippy side it's difficult to detect empty lines between an attributes and the + /// following item because empty lines and comments are not part of the AST. The parsing + /// currently works for basic cases but is not perfect. + /// + /// **Example:** + /// ```rust + /// // Good (as inner attribute) + /// #![inline(always)] + /// + /// fn this_is_fine() { } + /// + /// // Bad + /// #[inline(always)] + /// + /// fn not_quite_good_code() { } + /// + /// // Good (as outer attribute) + /// #[inline(always)] + /// fn this_is_fine_too() { } + /// ``` + pub EMPTY_LINE_AFTER_OUTER_ATTR, + nursery, + "empty line after outer attribute" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `allow`/`warn`/`deny`/`forbid` attributes with scoped clippy + /// lints and if those lints exist in clippy. If there is an uppercase letter in the lint name + /// (not the tool name) and a lowercase version of this lint exists, it will suggest to lowercase + /// the lint name. + /// + /// **Why is this bad?** A lint attribute with a mistyped lint name won't have an effect. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// #![warn(if_not_els)] + /// #![deny(clippy::All)] + /// ``` + /// + /// Good: + /// ```rust + /// #![warn(if_not_else)] + /// #![deny(clippy::all)] + /// ``` + pub UNKNOWN_CLIPPY_LINTS, + style, + "unknown_lints for scoped Clippy lints" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it + /// with `#[rustfmt::skip]`. + /// + /// **Why is this bad?** Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690)) + /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes. + /// + /// **Known problems:** This lint doesn't detect crate level inner attributes, because they get + /// processed before the PreExpansionPass lints get executed. See + /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765) + /// + /// **Example:** + /// + /// Bad: + /// ```rust + /// #[cfg_attr(rustfmt, rustfmt_skip)] + /// fn main() { } + /// ``` + /// + /// Good: + /// ```rust + /// #[rustfmt::skip] + /// fn main() { } + /// ``` + pub DEPRECATED_CFG_ATTR, + complexity, + "usage of `cfg_attr(rustfmt)` instead of tool attributes" +} + +declare_clippy_lint! { + /// **What it does:** Checks for cfg attributes having operating systems used in target family position. + /// + /// **Why is this bad?** The configuration option will not be recognised and the related item will not be included + /// by the conditional compilation engine. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust + /// #[cfg(linux)] + /// fn conditional() { } + /// ``` + /// + /// Good: + /// ```rust + /// #[cfg(target_os = "linux")] + /// fn conditional() { } + /// ``` + /// + /// Or: + /// ```rust + /// #[cfg(unix)] + /// fn conditional() { } + /// ``` + /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details. + pub MISMATCHED_TARGET_OS, + correctness, + "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`" +} + +declare_lint_pass!(Attributes => [ + INLINE_ALWAYS, + DEPRECATED_SEMVER, + USELESS_ATTRIBUTE, + EMPTY_LINE_AFTER_OUTER_ATTR, + UNKNOWN_CLIPPY_LINTS, +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Attributes { + fn check_attribute(&mut self, cx: &LateContext<'a, 'tcx>, attr: &'tcx Attribute) { + if let Some(items) = &attr.meta_item_list() { + if let Some(ident) = attr.ident() { + match &*ident.as_str() { + "allow" | "warn" | "deny" | "forbid" => { + check_clippy_lint_names(cx, items); + }, + _ => {}, + } + if items.is_empty() || !attr.check_name(sym!(deprecated)) { + return; + } + for item in items { + if_chain! { + if let NestedMetaItem::MetaItem(mi) = &item; + if let MetaItemKind::NameValue(lit) = &mi.kind; + if mi.check_name(sym!(since)); + then { + check_semver(cx, item.span(), lit); + } + } + } + } + } + } + + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if is_relevant_item(cx, item) { + check_attrs(cx, item.span, item.ident.name, &item.attrs) + } + match item.kind { + ItemKind::ExternCrate(..) | ItemKind::Use(..) => { + let skip_unused_imports = item.attrs.iter().any(|attr| attr.check_name(sym!(macro_use))); + + for attr in item.attrs { + if in_external_macro(cx.sess(), attr.span) { + return; + } + if let Some(lint_list) = &attr.meta_item_list() { + if let Some(ident) = attr.ident() { + match &*ident.as_str() { + "allow" | "warn" | "deny" | "forbid" => { + // whitelist `unused_imports`, `deprecated` and `unreachable_pub` for `use` items + // and `unused_imports` for `extern crate` items with `macro_use` + for lint in lint_list { + match item.kind { + ItemKind::Use(..) => { + if is_word(lint, sym!(unused_imports)) + || is_word(lint, sym!(deprecated)) + || is_word(lint, sym!(unreachable_pub)) + || is_word(lint, sym!(unused)) + { + return; + } + }, + ItemKind::ExternCrate(..) => { + if is_word(lint, sym!(unused_imports)) && skip_unused_imports { + return; + } + if is_word(lint, sym!(unused_extern_crates)) { + return; + } + }, + _ => {}, + } + } + let line_span = first_line_of_span(cx, attr.span); + + if let Some(mut sugg) = snippet_opt(cx, line_span) { + if sugg.contains("#[") { + span_lint_and_then( + cx, + USELESS_ATTRIBUTE, + line_span, + "useless lint attribute", + |diag| { + sugg = sugg.replacen("#[", "#![", 1); + diag.span_suggestion( + line_span, + "if you just forgot a `!`, use", + sugg, + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + }, + _ => {}, + } + } + } + } + }, + _ => {}, + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem<'_>) { + if is_relevant_impl(cx, item) { + check_attrs(cx, item.span, item.ident.name, &item.attrs) + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem<'_>) { + if is_relevant_trait(cx, item) { + check_attrs(cx, item.span, item.ident.name, &item.attrs) + } + } +} + +#[allow(clippy::single_match_else)] +fn check_clippy_lint_names(cx: &LateContext<'_, '_>, items: &[NestedMetaItem]) { + let lint_store = cx.lints(); + for lint in items { + if_chain! { + if let Some(meta_item) = lint.meta_item(); + if meta_item.path.segments.len() > 1; + if let tool_name = meta_item.path.segments[0].ident; + if tool_name.as_str() == "clippy"; + let name = meta_item.path.segments.last().unwrap().ident.name; + if let CheckLintNameResult::Tool(Err((None, _))) = lint_store.check_lint_name( + &name.as_str(), + Some(tool_name.name), + ); + then { + span_lint_and_then( + cx, + UNKNOWN_CLIPPY_LINTS, + lint.span(), + &format!("unknown clippy lint: clippy::{}", name), + |diag| { + let name_lower = name.as_str().to_lowercase(); + let symbols = lint_store.get_lints().iter().map( + |l| Symbol::intern(&l.name_lower()) + ).collect::>(); + let sugg = find_best_match_for_name( + symbols.iter(), + &format!("clippy::{}", name_lower), + None, + ); + if name.as_str().chars().any(char::is_uppercase) + && lint_store.find_lints(&format!("clippy::{}", name_lower)).is_ok() { + diag.span_suggestion( + lint.span(), + "lowercase the lint name", + format!("clippy::{}", name_lower), + Applicability::MachineApplicable, + ); + } else if let Some(sugg) = sugg { + diag.span_suggestion( + lint.span(), + "did you mean", + sugg.to_string(), + Applicability::MachineApplicable, + ); + } + } + ); + } + }; + } +} + +fn is_relevant_item(cx: &LateContext<'_, '_>, item: &Item<'_>) -> bool { + if let ItemKind::Fn(_, _, eid) = item.kind { + is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value) + } else { + true + } +} + +fn is_relevant_impl(cx: &LateContext<'_, '_>, item: &ImplItem<'_>) -> bool { + match item.kind { + ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value), + _ => false, + } +} + +fn is_relevant_trait(cx: &LateContext<'_, '_>, item: &TraitItem<'_>) -> bool { + match item.kind { + TraitItemKind::Fn(_, TraitFn::Required(_)) => true, + TraitItemKind::Fn(_, TraitFn::Provided(eid)) => { + is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value) + }, + _ => false, + } +} + +fn is_relevant_block(cx: &LateContext<'_, '_>, tables: &ty::TypeckTables<'_>, block: &Block<'_>) -> bool { + if let Some(stmt) = block.stmts.first() { + match &stmt.kind { + StmtKind::Local(_) => true, + StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, tables, expr), + _ => false, + } + } else { + block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e)) + } +} + +fn is_relevant_expr(cx: &LateContext<'_, '_>, tables: &ty::TypeckTables<'_>, expr: &Expr<'_>) -> bool { + match &expr.kind { + ExprKind::Block(block, _) => is_relevant_block(cx, tables, block), + ExprKind::Ret(Some(e)) => is_relevant_expr(cx, tables, e), + ExprKind::Ret(None) | ExprKind::Break(_, None) => false, + ExprKind::Call(path_expr, _) => { + if let ExprKind::Path(qpath) = &path_expr.kind { + if let Some(fun_id) = tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() { + !match_def_path(cx, fun_id, &paths::BEGIN_PANIC) + } else { + true + } + } else { + true + } + }, + _ => true, + } +} + +fn check_attrs(cx: &LateContext<'_, '_>, span: Span, name: Name, attrs: &[Attribute]) { + if span.from_expansion() { + return; + } + + for attr in attrs { + let attr_item = if let AttrKind::Normal(ref attr) = attr.kind { + attr + } else { + continue; + }; + + if attr.style == AttrStyle::Outer { + if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) { + return; + } + + let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt()); + let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt()); + + if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) { + let lines = snippet.split('\n').collect::>(); + let lines = without_block_comments(lines); + + if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 { + span_lint( + cx, + EMPTY_LINE_AFTER_OUTER_ATTR, + begin_of_attr_to_item, + "Found an empty line after an outer attribute. \ + Perhaps you forgot to add a `!` to make it an inner attribute?", + ); + } + } + } + + if let Some(values) = attr.meta_item_list() { + if values.len() != 1 || !attr.check_name(sym!(inline)) { + continue; + } + if is_word(&values[0], sym!(always)) { + span_lint( + cx, + INLINE_ALWAYS, + attr.span, + &format!( + "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea", + name + ), + ); + } + } + } +} + +fn check_semver(cx: &LateContext<'_, '_>, span: Span, lit: &Lit) { + if let LitKind::Str(is, _) = lit.kind { + if Version::parse(&is.as_str()).is_ok() { + return; + } + } + span_lint( + cx, + DEPRECATED_SEMVER, + span, + "the since field must contain a semver-compliant version", + ); +} + +fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool { + if let NestedMetaItem::MetaItem(mi) = &nmi { + mi.is_word() && mi.check_name(expected) + } else { + false + } +} + +declare_lint_pass!(EarlyAttributes => [DEPRECATED_CFG_ATTR, MISMATCHED_TARGET_OS]); + +impl EarlyLintPass for EarlyAttributes { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { + check_deprecated_cfg_attr(cx, attr); + check_mismatched_target_os(cx, attr); + } +} + +fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) { + if_chain! { + // check cfg_attr + if attr.check_name(sym!(cfg_attr)); + if let Some(items) = attr.meta_item_list(); + if items.len() == 2; + // check for `rustfmt` + if let Some(feature_item) = items[0].meta_item(); + if feature_item.check_name(sym!(rustfmt)); + // check for `rustfmt_skip` and `rustfmt::skip` + if let Some(skip_item) = &items[1].meta_item(); + if skip_item.check_name(sym!(rustfmt_skip)) || + skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym!(skip); + // Only lint outer attributes, because custom inner attributes are unstable + // Tracking issue: https://github.com/rust-lang/rust/issues/54726 + if let AttrStyle::Outer = attr.style; + then { + span_lint_and_sugg( + cx, + DEPRECATED_CFG_ATTR, + attr.span, + "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes", + "use", + "#[rustfmt::skip]".to_string(), + Applicability::MachineApplicable, + ); + } + } +} + +fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) { + fn find_os(name: &str) -> Option<&'static str> { + UNIX_SYSTEMS + .iter() + .chain(NON_UNIX_SYSTEMS.iter()) + .find(|&&os| os == name) + .copied() + } + + fn is_unix(name: &str) -> bool { + UNIX_SYSTEMS.iter().any(|&os| os == name) + } + + fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> { + let mut mismatched = Vec::new(); + + for item in items { + if let NestedMetaItem::MetaItem(meta) = item { + match &meta.kind { + MetaItemKind::List(list) => { + mismatched.extend(find_mismatched_target_os(&list)); + }, + MetaItemKind::Word => { + if_chain! { + if let Some(ident) = meta.ident(); + if let Some(os) = find_os(&*ident.name.as_str()); + then { + mismatched.push((os, ident.span)); + } + } + }, + _ => {}, + } + } + } + + mismatched + } + + if_chain! { + if attr.check_name(sym!(cfg)); + if let Some(list) = attr.meta_item_list(); + let mismatched = find_mismatched_target_os(&list); + if !mismatched.is_empty(); + then { + let mess = "operating system used in target family position"; + + span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, &mess, |diag| { + // Avoid showing the unix suggestion multiple times in case + // we have more than one mismatch for unix-like systems + let mut unix_suggested = false; + + for (os, span) in mismatched { + let sugg = format!("target_os = \"{}\"", os); + diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect); + + if !unix_suggested && is_unix(os) { + diag.help("Did you mean `unix`?"); + unix_suggested = true; + } + } + }); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/await_holding_lock.rs b/src/tools/clippy/clippy_lints/src/await_holding_lock.rs new file mode 100644 index 0000000000000..832910763e60c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/await_holding_lock.rs @@ -0,0 +1,97 @@ +use crate::utils::{match_def_path, paths, span_lint_and_note}; +use rustc_hir::def_id::DefId; +use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::GeneratorInteriorTypeCause; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for calls to await while holding a + /// non-async-aware MutexGuard. + /// + /// **Why is this bad?** The Mutex types found in syd::sync and parking_lot + /// are not designed to operator in an async context across await points. + /// + /// There are two potential solutions. One is to use an asynx-aware Mutex + /// type. Many asynchronous foundation crates provide such a Mutex type. The + /// other solution is to ensure the mutex is unlocked before calling await, + /// either by introducing a scope or an explicit call to Drop::drop. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// use std::sync::Mutex; + /// + /// async fn foo(x: &Mutex) { + /// let guard = x.lock().unwrap(); + /// *guard += 1; + /// bar.await; + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// use std::sync::Mutex; + /// + /// async fn foo(x: &Mutex) { + /// { + /// let guard = x.lock().unwrap(); + /// *guard += 1; + /// } + /// bar.await; + /// } + /// ``` + pub AWAIT_HOLDING_LOCK, + pedantic, + "Inside an async function, holding a MutexGuard while calling await" +} + +declare_lint_pass!(AwaitHoldingLock => [AWAIT_HOLDING_LOCK]); + +impl LateLintPass<'_, '_> for AwaitHoldingLock { + fn check_body(&mut self, cx: &LateContext<'_, '_>, body: &'_ Body<'_>) { + use AsyncGeneratorKind::{Block, Closure, Fn}; + match body.generator_kind { + Some(GeneratorKind::Async(Block)) + | Some(GeneratorKind::Async(Closure)) + | Some(GeneratorKind::Async(Fn)) => { + let body_id = BodyId { + hir_id: body.value.hir_id, + }; + let def_id = cx.tcx.hir().body_owner_def_id(body_id); + let tables = cx.tcx.typeck_tables_of(def_id); + check_interior_types(cx, &tables.generator_interior_types, body.value.span); + }, + _ => {}, + } + } +} + +fn check_interior_types(cx: &LateContext<'_, '_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) { + for ty_cause in ty_causes { + if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind { + if is_mutex_guard(cx, adt.did) { + span_lint_and_note( + cx, + AWAIT_HOLDING_LOCK, + ty_cause.span, + "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.", + ty_cause.scope_span.or(Some(span)), + "these are all the await points this lock is held through", + ); + } + } + } +} + +fn is_mutex_guard(cx: &LateContext<'_, '_>, def_id: DefId) -> bool { + match_def_path(cx, def_id, &paths::MUTEX_GUARD) + || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD) + || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD) + || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD) + || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD) + || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD) +} diff --git a/src/tools/clippy/clippy_lints/src/bit_mask.rs b/src/tools/clippy/clippy_lints/src/bit_mask.rs new file mode 100644 index 0000000000000..ccb62cb038fd0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/bit_mask.rs @@ -0,0 +1,326 @@ +use crate::consts::{constant, Constant}; +use crate::utils::sugg::Sugg; +use crate::utils::{span_lint, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for incompatible bit masks in comparisons. + /// + /// The formula for detecting if an expression of the type `_ m + /// c` (where `` is one of {`&`, `|`} and `` is one of + /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following + /// table: + /// + /// |Comparison |Bit Op|Example |is always|Formula | + /// |------------|------|------------|---------|----------------------| + /// |`==` or `!=`| `&` |`x & 2 == 3`|`false` |`c & m != c` | + /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` | + /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` | + /// |`==` or `!=`| `|` |`x | 1 == 0`|`false` |`c | m != c` | + /// |`<` or `>=`| `|` |`x | 1 < 1` |`false` |`m >= c` | + /// |`<=` or `>` | `|` |`x | 1 > 0` |`true` |`m > c` | + /// + /// **Why is this bad?** If the bits that the comparison cares about are always + /// set to zero or one by the bit mask, the comparison is constant `true` or + /// `false` (depending on mask, compared value, and operators). + /// + /// So the code is actively misleading, and the only reason someone would write + /// this intentionally is to win an underhanded Rust contest or create a + /// test-case for this lint. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if (x & 1 == 2) { } + /// ``` + pub BAD_BIT_MASK, + correctness, + "expressions of the form `_ & mask == select` that will only ever return `true` or `false`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bit masks in comparisons which can be removed + /// without changing the outcome. The basic structure can be seen in the + /// following table: + /// + /// |Comparison| Bit Op |Example |equals | + /// |----------|---------|-----------|-------| + /// |`>` / `<=`|`|` / `^`|`x | 2 > 3`|`x > 3`| + /// |`<` / `>=`|`|` / `^`|`x ^ 1 < 4`|`x < 4`| + /// + /// **Why is this bad?** Not equally evil as [`bad_bit_mask`](#bad_bit_mask), + /// but still a bit misleading, because the bit mask is ineffective. + /// + /// **Known problems:** False negatives: This lint will only match instances + /// where we have figured out the math (which is for a power-of-two compared + /// value). This means things like `x | 1 >= 7` (which would be better written + /// as `x >= 6`) will not be reported (but bit masks like this are fairly + /// uncommon). + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if (x | 1 > 3) { } + /// ``` + pub INEFFECTIVE_BIT_MASK, + correctness, + "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bit masks that can be replaced by a call + /// to `trailing_zeros` + /// + /// **Why is this bad?** `x.trailing_zeros() > 4` is much clearer than `x & 15 + /// == 0` + /// + /// **Known problems:** llvm generates better code for `x & 15 == 0` on x86 + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if x & 0b1111 == 0 { } + /// ``` + pub VERBOSE_BIT_MASK, + style, + "expressions where a bit mask is less readable than the corresponding method call" +} + +#[derive(Copy, Clone)] +pub struct BitMask { + verbose_bit_mask_threshold: u64, +} + +impl BitMask { + #[must_use] + pub fn new(verbose_bit_mask_threshold: u64) -> Self { + Self { + verbose_bit_mask_threshold, + } + } +} + +impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BitMask { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Binary(cmp, left, right) = &e.kind { + if cmp.node.is_comparison() { + if let Some(cmp_opt) = fetch_int_literal(cx, right) { + check_compare(cx, left, cmp.node, cmp_opt, e.span) + } else if let Some(cmp_val) = fetch_int_literal(cx, left) { + check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span) + } + } + } + if_chain! { + if let ExprKind::Binary(op, left, right) = &e.kind; + if BinOpKind::Eq == op.node; + if let ExprKind::Binary(op1, left1, right1) = &left.kind; + if BinOpKind::BitAnd == op1.node; + if let ExprKind::Lit(lit) = &right1.kind; + if let LitKind::Int(n, _) = lit.node; + if let ExprKind::Lit(lit1) = &right.kind; + if let LitKind::Int(0, _) = lit1.node; + if n.leading_zeros() == n.count_zeros(); + if n > u128::from(self.verbose_bit_mask_threshold); + then { + span_lint_and_then(cx, + VERBOSE_BIT_MASK, + e.span, + "bit mask could be simplified with a call to `trailing_zeros`", + |diag| { + let sugg = Sugg::hir(cx, left1, "...").maybe_par(); + diag.span_suggestion( + e.span, + "try", + format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()), + Applicability::MaybeIncorrect, + ); + }); + } + } + } +} + +#[must_use] +fn invert_cmp(cmp: BinOpKind) -> BinOpKind { + match cmp { + BinOpKind::Eq => BinOpKind::Eq, + BinOpKind::Ne => BinOpKind::Ne, + BinOpKind::Lt => BinOpKind::Gt, + BinOpKind::Gt => BinOpKind::Lt, + BinOpKind::Le => BinOpKind::Ge, + BinOpKind::Ge => BinOpKind::Le, + _ => BinOpKind::Or, // Dummy + } +} + +fn check_compare(cx: &LateContext<'_, '_>, bit_op: &Expr<'_>, cmp_op: BinOpKind, cmp_value: u128, span: Span) { + if let ExprKind::Binary(op, left, right) = &bit_op.kind { + if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr { + return; + } + fetch_int_literal(cx, right) + .or_else(|| fetch_int_literal(cx, left)) + .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span)) + } +} + +#[allow(clippy::too_many_lines)] +fn check_bit_mask( + cx: &LateContext<'_, '_>, + bit_op: BinOpKind, + cmp_op: BinOpKind, + mask_value: u128, + cmp_value: u128, + span: Span, +) { + match cmp_op { + BinOpKind::Eq | BinOpKind::Ne => match bit_op { + BinOpKind::BitAnd => { + if mask_value & cmp_value != cmp_value { + if cmp_value != 0 { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ & {}` can never be equal to `{}`", + mask_value, cmp_value + ), + ); + } + } else if mask_value == 0 { + span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero"); + } + }, + BinOpKind::BitOr => { + if mask_value | cmp_value != cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ | {}` can never be equal to `{}`", + mask_value, cmp_value + ), + ); + } + }, + _ => (), + }, + BinOpKind::Lt | BinOpKind::Ge => match bit_op { + BinOpKind::BitAnd => { + if mask_value < cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ & {}` will always be lower than `{}`", + mask_value, cmp_value + ), + ); + } else if mask_value == 0 { + span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero"); + } + }, + BinOpKind::BitOr => { + if mask_value >= cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ | {}` will never be lower than `{}`", + mask_value, cmp_value + ), + ); + } else { + check_ineffective_lt(cx, span, mask_value, cmp_value, "|"); + } + }, + BinOpKind::BitXor => check_ineffective_lt(cx, span, mask_value, cmp_value, "^"), + _ => (), + }, + BinOpKind::Le | BinOpKind::Gt => match bit_op { + BinOpKind::BitAnd => { + if mask_value <= cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ & {}` will never be higher than `{}`", + mask_value, cmp_value + ), + ); + } else if mask_value == 0 { + span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero"); + } + }, + BinOpKind::BitOr => { + if mask_value > cmp_value { + span_lint( + cx, + BAD_BIT_MASK, + span, + &format!( + "incompatible bit mask: `_ | {}` will always be higher than `{}`", + mask_value, cmp_value + ), + ); + } else { + check_ineffective_gt(cx, span, mask_value, cmp_value, "|"); + } + }, + BinOpKind::BitXor => check_ineffective_gt(cx, span, mask_value, cmp_value, "^"), + _ => (), + }, + _ => (), + } +} + +fn check_ineffective_lt(cx: &LateContext<'_, '_>, span: Span, m: u128, c: u128, op: &str) { + if c.is_power_of_two() && m < c { + span_lint( + cx, + INEFFECTIVE_BIT_MASK, + span, + &format!( + "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly", + op, m, c + ), + ); + } +} + +fn check_ineffective_gt(cx: &LateContext<'_, '_>, span: Span, m: u128, c: u128, op: &str) { + if (c + 1).is_power_of_two() && m <= c { + span_lint( + cx, + INEFFECTIVE_BIT_MASK, + span, + &format!( + "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly", + op, m, c + ), + ); + } +} + +fn fetch_int_literal(cx: &LateContext<'_, '_>, lit: &Expr<'_>) -> Option { + match constant(cx, cx.tables, lit)?.0 { + Constant::Int(n) => Some(n), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/blacklisted_name.rs b/src/tools/clippy/clippy_lints/src/blacklisted_name.rs new file mode 100644 index 0000000000000..6f26f031431db --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/blacklisted_name.rs @@ -0,0 +1,51 @@ +use crate::utils::span_lint; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of blacklisted names for variables, such + /// as `foo`. + /// + /// **Why is this bad?** These names are usually placeholder names and should be + /// avoided. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let foo = 3.14; + /// ``` + pub BLACKLISTED_NAME, + style, + "usage of a blacklisted/placeholder name" +} + +#[derive(Clone, Debug)] +pub struct BlacklistedName { + blacklist: FxHashSet, +} + +impl BlacklistedName { + pub fn new(blacklist: FxHashSet) -> Self { + Self { blacklist } + } +} + +impl_lint_pass!(BlacklistedName => [BLACKLISTED_NAME]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlacklistedName { + fn check_pat(&mut self, cx: &LateContext<'a, 'tcx>, pat: &'tcx Pat<'_>) { + if let PatKind::Binding(.., ident, _) = pat.kind { + if self.blacklist.contains(&ident.name.to_string()) { + span_lint( + cx, + BLACKLISTED_NAME, + ident.span, + &format!("use of a blacklisted/placeholder name `{}`", ident.name), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/block_in_if_condition.rs b/src/tools/clippy/clippy_lints/src/block_in_if_condition.rs new file mode 100644 index 0000000000000..9e533eaa32c93 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/block_in_if_condition.rs @@ -0,0 +1,148 @@ +use crate::utils::{differing_macro_contexts, higher, snippet_block_with_applicability, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BlockCheckMode, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `if` conditions that use blocks to contain an + /// expression. + /// + /// **Why is this bad?** It isn't really Rust style, same as using parentheses + /// to contain expressions. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// if { true } { /* ... */ } + /// ``` + pub BLOCK_IN_IF_CONDITION_EXPR, + style, + "braces that can be eliminated in conditions, e.g., `if { true } ...`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `if` conditions that use blocks containing + /// statements, or conditions that use closures with blocks. + /// + /// **Why is this bad?** Using blocks in the condition makes it hard to read. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// if { let x = somefunc(); x } {} + /// // or + /// if somefunc(|x| { x == 47 }) {} + /// ``` + pub BLOCK_IN_IF_CONDITION_STMT, + style, + "complex blocks in conditions, e.g., `if { let x = true; x } ...`" +} + +declare_lint_pass!(BlockInIfCondition => [BLOCK_IN_IF_CONDITION_EXPR, BLOCK_IN_IF_CONDITION_STMT]); + +struct ExVisitor<'a, 'tcx> { + found_block: Option<&'tcx Expr<'tcx>>, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Closure(_, _, eid, _, _) = expr.kind { + let body = self.cx.tcx.hir().body(eid); + let ex = &body.value; + if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() { + self.found_block = Some(ex); + return; + } + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition"; +const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \ + instead, move the block or closure higher and bind it with a `let`"; + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BlockInIfCondition { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + if let Some((cond, _, _)) = higher::if_block(&expr) { + if let ExprKind::Block(block, _) = &cond.kind { + if block.rules == BlockCheckMode::DefaultBlock { + if block.stmts.is_empty() { + if let Some(ex) = &block.expr { + // don't dig into the expression here, just suggest that they remove + // the block + if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) { + return; + } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BLOCK_IN_IF_CONDITION_EXPR, + cond.span, + BRACED_EXPR_MESSAGE, + "try", + format!( + "{}", + snippet_block_with_applicability( + cx, + ex.span, + "..", + Some(expr.span), + &mut applicability + ) + ), + applicability, + ); + } + } else { + let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span); + if span.from_expansion() || differing_macro_contexts(expr.span, span) { + return; + } + // move block higher + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BLOCK_IN_IF_CONDITION_STMT, + expr.span.with_hi(cond.span.hi()), + COMPLEX_BLOCK_MESSAGE, + "try", + format!( + "let res = {}; if res", + snippet_block_with_applicability( + cx, + block.span, + "..", + Some(expr.span), + &mut applicability + ), + ), + applicability, + ); + } + } + } else { + let mut visitor = ExVisitor { found_block: None, cx }; + walk_expr(&mut visitor, cond); + if let Some(block) = visitor.found_block { + span_lint(cx, BLOCK_IN_IF_CONDITION_STMT, block.span, COMPLEX_BLOCK_MESSAGE); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs new file mode 100644 index 0000000000000..8031052e073d5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/booleans.rs @@ -0,0 +1,499 @@ +use crate::utils::{ + get_trait_def_id, implements_trait, in_macro, is_type_diagnostic_item, paths, snippet_opt, span_lint_and_sugg, + span_lint_and_then, SpanlessEq, +}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for boolean expressions that can be written more + /// concisely. + /// + /// **Why is this bad?** Readability of boolean expressions suffers from + /// unnecessary duplication. + /// + /// **Known problems:** Ignores short circuiting behavior of `||` and + /// `&&`. Ignores `|`, `&` and `^`. + /// + /// **Example:** + /// ```ignore + /// if a && true // should be: if a + /// if !(a == b) // should be: if a != b + /// ``` + pub NONMINIMAL_BOOL, + complexity, + "boolean expressions that can be written more concisely" +} + +declare_clippy_lint! { + /// **What it does:** Checks for boolean expressions that contain terminals that + /// can be eliminated. + /// + /// **Why is this bad?** This is most likely a logic bug. + /// + /// **Known problems:** Ignores short circuiting behavior. + /// + /// **Example:** + /// ```ignore + /// if a && b || a { ... } + /// ``` + /// The `b` is unnecessary, the expression is equivalent to `if a`. + pub LOGIC_BUG, + correctness, + "boolean expressions that contain terminals which can be eliminated" +} + +// For each pairs, both orders are considered. +const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")]; + +declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, LOGIC_BUG]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonminimalBool { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + NonminimalBoolVisitor { cx }.visit_body(body) + } +} + +struct NonminimalBoolVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, +} + +use quine_mc_cluskey::Bool; +struct Hir2Qmm<'a, 'tcx, 'v> { + terminals: Vec<&'v Expr<'v>>, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> { + fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec) -> Result, String> { + for a in a { + if let ExprKind::Binary(binop, lhs, rhs) = &a.kind { + if binop.node == op { + v = self.extract(op, &[lhs, rhs], v)?; + continue; + } + } + v.push(self.run(a)?); + } + Ok(v) + } + + fn run(&mut self, e: &'v Expr<'_>) -> Result { + fn negate(bin_op_kind: BinOpKind) -> Option { + match bin_op_kind { + BinOpKind::Eq => Some(BinOpKind::Ne), + BinOpKind::Ne => Some(BinOpKind::Eq), + BinOpKind::Gt => Some(BinOpKind::Le), + BinOpKind::Ge => Some(BinOpKind::Lt), + BinOpKind::Lt => Some(BinOpKind::Ge), + BinOpKind::Le => Some(BinOpKind::Gt), + _ => None, + } + } + + // prevent folding of `cfg!` macros and the like + if !e.span.from_expansion() { + match &e.kind { + ExprKind::Unary(UnOp::UnNot, inner) => return Ok(Bool::Not(box self.run(inner)?)), + ExprKind::Binary(binop, lhs, rhs) => match &binop.node { + BinOpKind::Or => return Ok(Bool::Or(self.extract(BinOpKind::Or, &[lhs, rhs], Vec::new())?)), + BinOpKind::And => return Ok(Bool::And(self.extract(BinOpKind::And, &[lhs, rhs], Vec::new())?)), + _ => (), + }, + ExprKind::Lit(lit) => match lit.node { + LitKind::Bool(true) => return Ok(Bool::True), + LitKind::Bool(false) => return Ok(Bool::False), + _ => (), + }, + _ => (), + } + } + for (n, expr) in self.terminals.iter().enumerate() { + if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) { + #[allow(clippy::cast_possible_truncation)] + return Ok(Bool::Term(n as u8)); + } + + if_chain! { + if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind; + if implements_ord(self.cx, e_lhs); + if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind; + if negate(e_binop.node) == Some(expr_binop.node); + if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e_lhs, expr_lhs); + if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e_rhs, expr_rhs); + then { + #[allow(clippy::cast_possible_truncation)] + return Ok(Bool::Not(Box::new(Bool::Term(n as u8)))); + } + } + } + let n = self.terminals.len(); + self.terminals.push(e); + if n < 32 { + #[allow(clippy::cast_possible_truncation)] + Ok(Bool::Term(n as u8)) + } else { + Err("too many literals".to_owned()) + } + } +} + +struct SuggestContext<'a, 'tcx, 'v> { + terminals: &'v [&'v Expr<'v>], + cx: &'a LateContext<'a, 'tcx>, + output: String, +} + +impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> { + fn recurse(&mut self, suggestion: &Bool) -> Option<()> { + use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True}; + match suggestion { + True => { + self.output.push_str("true"); + }, + False => { + self.output.push_str("false"); + }, + Not(inner) => match **inner { + And(_) | Or(_) => { + self.output.push('!'); + self.output.push('('); + self.recurse(inner); + self.output.push(')'); + }, + Term(n) => { + let terminal = self.terminals[n as usize]; + if let Some(str) = simplify_not(self.cx, terminal) { + self.output.push_str(&str) + } else { + self.output.push('!'); + let snip = snippet_opt(self.cx, terminal.span)?; + self.output.push_str(&snip); + } + }, + True | False | Not(_) => { + self.output.push('!'); + self.recurse(inner)?; + }, + }, + And(v) => { + for (index, inner) in v.iter().enumerate() { + if index > 0 { + self.output.push_str(" && "); + } + if let Or(_) = *inner { + self.output.push('('); + self.recurse(inner); + self.output.push(')'); + } else { + self.recurse(inner); + } + } + }, + Or(v) => { + for (index, inner) in v.iter().rev().enumerate() { + if index > 0 { + self.output.push_str(" || "); + } + self.recurse(inner); + } + }, + &Term(n) => { + let snip = snippet_opt(self.cx, self.terminals[n as usize].span)?; + self.output.push_str(&snip); + }, + } + Some(()) + } +} + +fn simplify_not(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::Binary(binop, lhs, rhs) => { + if !implements_ord(cx, lhs) { + return None; + } + + match binop.node { + BinOpKind::Eq => Some(" != "), + BinOpKind::Ne => Some(" == "), + BinOpKind::Lt => Some(" >= "), + BinOpKind::Gt => Some(" <= "), + BinOpKind::Le => Some(" > "), + BinOpKind::Ge => Some(" < "), + _ => None, + } + .and_then(|op| { + Some(format!( + "{}{}{}", + snippet_opt(cx, lhs.span)?, + op, + snippet_opt(cx, rhs.span)? + )) + }) + }, + ExprKind::MethodCall(path, _, args) if args.len() == 1 => { + let type_of_receiver = cx.tables.expr_ty(&args[0]); + if !is_type_diagnostic_item(cx, type_of_receiver, sym!(option_type)) + && !is_type_diagnostic_item(cx, type_of_receiver, sym!(result_type)) + { + return None; + } + METHODS_WITH_NEGATION + .iter() + .cloned() + .flat_map(|(a, b)| vec![(a, b), (b, a)]) + .find(|&(a, _)| { + let path: &str = &path.ident.name.as_str(); + a == path + }) + .and_then(|(_, neg_method)| Some(format!("{}.{}()", snippet_opt(cx, args[0].span)?, neg_method))) + }, + _ => None, + } +} + +fn suggest(cx: &LateContext<'_, '_>, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String { + let mut suggest_context = SuggestContext { + terminals, + cx, + output: String::new(), + }; + suggest_context.recurse(suggestion); + suggest_context.output +} + +fn simple_negate(b: Bool) -> Bool { + use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True}; + match b { + True => False, + False => True, + t @ Term(_) => Not(Box::new(t)), + And(mut v) => { + for el in &mut v { + *el = simple_negate(::std::mem::replace(el, True)); + } + Or(v) + }, + Or(mut v) => { + for el in &mut v { + *el = simple_negate(::std::mem::replace(el, True)); + } + And(v) + }, + Not(inner) => *inner, + } +} + +#[derive(Default)] +struct Stats { + terminals: [usize; 32], + negations: usize, + ops: usize, +} + +fn terminal_stats(b: &Bool) -> Stats { + fn recurse(b: &Bool, stats: &mut Stats) { + match b { + True | False => stats.ops += 1, + Not(inner) => { + match **inner { + And(_) | Or(_) => stats.ops += 1, // brackets are also operations + _ => stats.negations += 1, + } + recurse(inner, stats); + }, + And(v) | Or(v) => { + stats.ops += v.len() - 1; + for inner in v { + recurse(inner, stats); + } + }, + &Term(n) => stats.terminals[n as usize] += 1, + } + } + use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True}; + let mut stats = Stats::default(); + recurse(b, &mut stats); + stats +} + +impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { + fn bool_expr(&self, e: &'tcx Expr<'_>) { + let mut h2q = Hir2Qmm { + terminals: Vec::new(), + cx: self.cx, + }; + if let Ok(expr) = h2q.run(e) { + if h2q.terminals.len() > 8 { + // QMC has exponentially slow behavior as the number of terminals increases + // 8 is reasonable, it takes approximately 0.2 seconds. + // See #825 + return; + } + + let stats = terminal_stats(&expr); + let mut simplified = expr.simplify(); + for simple in Bool::Not(Box::new(expr)).simplify() { + match simple { + Bool::Not(_) | Bool::True | Bool::False => {}, + _ => simplified.push(Bool::Not(Box::new(simple.clone()))), + } + let simple_negated = simple_negate(simple); + if simplified.iter().any(|s| *s == simple_negated) { + continue; + } + simplified.push(simple_negated); + } + let mut improvements = Vec::with_capacity(simplified.len()); + 'simplified: for suggestion in &simplified { + let simplified_stats = terminal_stats(suggestion); + let mut improvement = false; + for i in 0..32 { + // ignore any "simplifications" that end up requiring a terminal more often + // than in the original expression + if stats.terminals[i] < simplified_stats.terminals[i] { + continue 'simplified; + } + if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 { + span_lint_and_then( + self.cx, + LOGIC_BUG, + e.span, + "this boolean expression contains a logic bug", + |diag| { + diag.span_help( + h2q.terminals[i].span, + "this expression can be optimized out by applying boolean operations to the \ + outer expression", + ); + diag.span_suggestion( + e.span, + "it would look like the following", + suggest(self.cx, suggestion, &h2q.terminals), + // nonminimal_bool can produce minimal but + // not human readable expressions (#3141) + Applicability::Unspecified, + ); + }, + ); + // don't also lint `NONMINIMAL_BOOL` + return; + } + // if the number of occurrences of a terminal decreases or any of the stats + // decreases while none increases + improvement |= (stats.terminals[i] > simplified_stats.terminals[i]) + || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops) + || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations); + } + if improvement { + improvements.push(suggestion); + } + } + let nonminimal_bool_lint = |suggestions: Vec<_>| { + span_lint_and_then( + self.cx, + NONMINIMAL_BOOL, + e.span, + "this boolean expression can be simplified", + |diag| { + diag.span_suggestions( + e.span, + "try", + suggestions.into_iter(), + // nonminimal_bool can produce minimal but + // not human readable expressions (#3141) + Applicability::Unspecified, + ); + }, + ); + }; + if improvements.is_empty() { + let mut visitor = NotSimplificationVisitor { cx: self.cx }; + visitor.visit_expr(e); + } else { + nonminimal_bool_lint( + improvements + .into_iter() + .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals)) + .collect(), + ); + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if in_macro(e.span) { + return; + } + match &e.kind { + ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => { + self.bool_expr(e) + }, + ExprKind::Unary(UnOp::UnNot, inner) => { + if self.cx.tables.node_types()[inner.hir_id].is_bool() { + self.bool_expr(e); + } else { + walk_expr(self, e); + } + }, + _ => walk_expr(self, e), + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn implements_ord<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, expr: &Expr<'_>) -> bool { + let ty = cx.tables.expr_ty(expr); + get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[])) +} + +struct NotSimplificationVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if let ExprKind::Unary(UnOp::UnNot, inner) = &expr.kind { + if let Some(suggestion) = simplify_not(self.cx, inner) { + span_lint_and_sugg( + self.cx, + NONMINIMAL_BOOL, + expr.span, + "this boolean expression can be simplified", + "try", + suggestion, + Applicability::MachineApplicable, + ); + } + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/bytecount.rs b/src/tools/clippy/clippy_lints/src/bytecount.rs new file mode 100644 index 0000000000000..91d3e47d7870c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/bytecount.rs @@ -0,0 +1,117 @@ +use crate::utils::{ + contains_name, get_pat_name, match_type, paths, single_segment_path, snippet_with_applicability, + span_lint_and_sugg, walk_ptrs_ty, +}; +use if_chain::if_chain; +use rustc_ast::ast::{Name, UintTy}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for naive byte counts + /// + /// **Why is this bad?** The [`bytecount`](https://crates.io/crates/bytecount) + /// crate has methods to count your bytes faster, especially for large slices. + /// + /// **Known problems:** If you have predominantly small slices, the + /// `bytecount::count(..)` method may actually be slower. However, if you can + /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be + /// faster in those cases. + /// + /// **Example:** + /// + /// ```rust + /// # let vec = vec![1_u8]; + /// &vec.iter().filter(|x| **x == 0u8).count(); // use bytecount::count instead + /// ``` + pub NAIVE_BYTECOUNT, + perf, + "use of naive `.filter(|&x| x == y).count()` to count byte values" +} + +declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ByteCount { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(ref count, _, ref count_args) = expr.kind; + if count.ident.name == sym!(count); + if count_args.len() == 1; + if let ExprKind::MethodCall(ref filter, _, ref filter_args) = count_args[0].kind; + if filter.ident.name == sym!(filter); + if filter_args.len() == 2; + if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind; + then { + let body = cx.tcx.hir().body(body_id); + if_chain! { + if body.params.len() == 1; + if let Some(argname) = get_pat_name(&body.params[0].pat); + if let ExprKind::Binary(ref op, ref l, ref r) = body.value.kind; + if op.node == BinOpKind::Eq; + if match_type(cx, + walk_ptrs_ty(cx.tables.expr_ty(&filter_args[0])), + &paths::SLICE_ITER); + then { + let needle = match get_path_name(l) { + Some(name) if check_arg(name, argname, r) => r, + _ => match get_path_name(r) { + Some(name) if check_arg(name, argname, l) => l, + _ => { return; } + } + }; + if ty::Uint(UintTy::U8) != walk_ptrs_ty(cx.tables.expr_ty(needle)).kind { + return; + } + let haystack = if let ExprKind::MethodCall(ref path, _, ref args) = + filter_args[0].kind { + let p = path.ident.name; + if (p == sym!(iter) || p == sym!(iter_mut)) && args.len() == 1 { + &args[0] + } else { + &filter_args[0] + } + } else { + &filter_args[0] + }; + let mut applicability = Applicability::MaybeIncorrect; + span_lint_and_sugg( + cx, + NAIVE_BYTECOUNT, + expr.span, + "You appear to be counting bytes the naive way", + "Consider using the bytecount crate", + format!("bytecount::count({}, {})", + snippet_with_applicability(cx, haystack.span, "..", &mut applicability), + snippet_with_applicability(cx, needle.span, "..", &mut applicability)), + applicability, + ); + } + }; + } + }; + } +} + +fn check_arg(name: Name, arg: Name, needle: &Expr<'_>) -> bool { + name == arg && !contains_name(name, needle) +} + +fn get_path_name(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) | ExprKind::Unary(UnOp::UnDeref, ref e) => { + get_path_name(e) + }, + ExprKind::Block(ref b, _) => { + if b.stmts.is_empty() { + b.expr.as_ref().and_then(|p| get_path_name(p)) + } else { + None + } + }, + ExprKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs b/src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs new file mode 100644 index 0000000000000..782da249808d0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs @@ -0,0 +1,105 @@ +//! lint on missing cargo common metadata + +use std::path::PathBuf; + +use crate::utils::{run_lints, span_lint}; +use rustc_hir::{hir_id::CRATE_HIR_ID, Crate}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::DUMMY_SP; + +declare_clippy_lint! { + /// **What it does:** Checks to see if all common metadata is defined in + /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata + /// + /// **Why is this bad?** It will be more difficult for users to discover the + /// purpose of the crate, and key information related to it. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```toml + /// # This `Cargo.toml` is missing an authors field: + /// [package] + /// name = "clippy" + /// version = "0.0.212" + /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" + /// repository = "https://github.com/rust-lang/rust-clippy" + /// readme = "README.md" + /// license = "MIT OR Apache-2.0" + /// keywords = ["clippy", "lint", "plugin"] + /// categories = ["development-tools", "development-tools::cargo-plugins"] + /// ``` + pub CARGO_COMMON_METADATA, + cargo, + "common metadata is defined in `Cargo.toml`" +} + +fn warning(cx: &LateContext<'_, '_>, message: &str) { + span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, message); +} + +fn missing_warning(cx: &LateContext<'_, '_>, package: &cargo_metadata::Package, field: &str) { + let message = format!("package `{}` is missing `{}` metadata", package.name, field); + warning(cx, &message); +} + +fn is_empty_str(value: &Option) -> bool { + value.as_ref().map_or(true, String::is_empty) +} + +fn is_empty_path(value: &Option) -> bool { + value.as_ref().and_then(|x| x.to_str()).map_or(true, str::is_empty) +} + +fn is_empty_vec(value: &[String]) -> bool { + // This works because empty iterators return true + value.iter().all(String::is_empty) +} + +declare_lint_pass!(CargoCommonMetadata => [CARGO_COMMON_METADATA]); + +impl LateLintPass<'_, '_> for CargoCommonMetadata { + fn check_crate(&mut self, cx: &LateContext<'_, '_>, _: &Crate<'_>) { + if !run_lints(cx, &[CARGO_COMMON_METADATA], CRATE_HIR_ID) { + return; + } + + let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().no_deps().exec() { + metadata + } else { + warning(cx, "could not read cargo metadata"); + return; + }; + + for package in metadata.packages { + if is_empty_vec(&package.authors) { + missing_warning(cx, &package, "package.authors"); + } + + if is_empty_str(&package.description) { + missing_warning(cx, &package, "package.description"); + } + + if is_empty_str(&package.license) && is_empty_path(&package.license_file) { + missing_warning(cx, &package, "either package.license or package.license_file"); + } + + if is_empty_str(&package.repository) { + missing_warning(cx, &package, "package.repository"); + } + + if is_empty_path(&package.readme) { + missing_warning(cx, &package, "package.readme"); + } + + if is_empty_vec(&package.keywords) { + missing_warning(cx, &package, "package.keywords"); + } + + if is_empty_vec(&package.categories) { + missing_warning(cx, &package, "package.categories"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/checked_conversions.rs b/src/tools/clippy/clippy_lints/src/checked_conversions.rs new file mode 100644 index 0000000000000..d9776dd50a836 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/checked_conversions.rs @@ -0,0 +1,345 @@ +//! lint on manually implemented checked conversions that could be transformed into `try_from` + +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{snippet_with_applicability, span_lint_and_sugg, SpanlessEq}; + +declare_clippy_lint! { + /// **What it does:** Checks for explicit bounds checking when casting. + /// + /// **Why is this bad?** Reduces the readability of statements & is error prone. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let foo: u32 = 5; + /// # let _ = + /// foo <= i32::MAX as u32 + /// # ; + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # use std::convert::TryFrom; + /// # let foo = 1; + /// # let _ = + /// i32::try_from(foo).is_ok() + /// # ; + /// ``` + pub CHECKED_CONVERSIONS, + pedantic, + "`try_from` could replace manual bounds checking when casting" +} + +declare_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CheckedConversions { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, item: &Expr<'_>) { + let result = if_chain! { + if !in_external_macro(cx.sess(), item.span); + if let ExprKind::Binary(op, ref left, ref right) = &item.kind; + + then { + match op.node { + BinOpKind::Ge | BinOpKind::Le => single_check(item), + BinOpKind::And => double_check(cx, left, right), + _ => None, + } + } else { + None + } + }; + + if_chain! { + if let Some(cv) = result; + if let Some(to_type) = cv.to_type; + + then { + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut + applicability); + span_lint_and_sugg( + cx, + CHECKED_CONVERSIONS, + item.span, + "Checked cast can be simplified.", + "try", + format!("{}::try_from({}).is_ok()", + to_type, + snippet), + applicability + ); + } + } + } +} + +/// Searches for a single check from unsigned to _ is done +/// todo: check for case signed -> larger unsigned == only x >= 0 +fn single_check<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { + check_upper_bound(expr).filter(|cv| cv.cvt == ConversionType::FromUnsigned) +} + +/// Searches for a combination of upper & lower bound checks +fn double_check<'a>(cx: &LateContext<'_, '_>, left: &'a Expr<'_>, right: &'a Expr<'_>) -> Option> { + let upper_lower = |l, r| { + let upper = check_upper_bound(l); + let lower = check_lower_bound(r); + + transpose(upper, lower).and_then(|(l, r)| l.combine(r, cx)) + }; + + upper_lower(left, right).or_else(|| upper_lower(right, left)) +} + +/// Contains the result of a tried conversion check +#[derive(Clone, Debug)] +struct Conversion<'a> { + cvt: ConversionType, + expr_to_cast: &'a Expr<'a>, + to_type: Option<&'a str>, +} + +/// The kind of conversion that is checked +#[derive(Copy, Clone, Debug, PartialEq)] +enum ConversionType { + SignedToUnsigned, + SignedToSigned, + FromUnsigned, +} + +impl<'a> Conversion<'a> { + /// Combine multiple conversions if the are compatible + pub fn combine(self, other: Self, cx: &LateContext<'_, '_>) -> Option> { + if self.is_compatible(&other, cx) { + // Prefer a Conversion that contains a type-constraint + Some(if self.to_type.is_some() { self } else { other }) + } else { + None + } + } + + /// Checks if two conversions are compatible + /// same type of conversion, same 'castee' and same 'to type' + pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_, '_>) -> bool { + (self.cvt == other.cvt) + && (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast)) + && (self.has_compatible_to_type(other)) + } + + /// Checks if the to-type is the same (if there is a type constraint) + fn has_compatible_to_type(&self, other: &Self) -> bool { + transpose(self.to_type.as_ref(), other.to_type.as_ref()).map_or(true, |(l, r)| l == r) + } + + /// Try to construct a new conversion if the conversion type is valid + fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option> { + ConversionType::try_new(from_type, to_type).map(|cvt| Conversion { + cvt, + expr_to_cast, + to_type: Some(to_type), + }) + } + + /// Construct a new conversion without type constraint + fn new_any(expr_to_cast: &'a Expr<'_>) -> Conversion<'a> { + Conversion { + cvt: ConversionType::SignedToUnsigned, + expr_to_cast, + to_type: None, + } + } +} + +impl ConversionType { + /// Creates a conversion type if the type is allowed & conversion is valid + #[must_use] + fn try_new(from: &str, to: &str) -> Option { + if UINTS.contains(&from) { + Some(Self::FromUnsigned) + } else if SINTS.contains(&from) { + if UINTS.contains(&to) { + Some(Self::SignedToUnsigned) + } else if SINTS.contains(&to) { + Some(Self::SignedToSigned) + } else { + None + } + } else { + None + } + } +} + +/// Check for `expr <= (to_type::MAX as from_type)` +fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { + if_chain! { + if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind; + if let Some((candidate, check)) = normalize_le_ge(op, left, right); + if let Some((from, to)) = get_types_from_cast(check, MAX_VALUE, INTS); + + then { + Conversion::try_new(candidate, from, to) + } else { + None + } + } +} + +/// Check for `expr >= 0|(to_type::MIN as from_type)` +fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { + fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option> { + (check_lower_bound_zero(candidate, check)).or_else(|| (check_lower_bound_min(candidate, check))) + } + + // First of we need a binary containing the expression & the cast + if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind { + normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r)) + } else { + None + } +} + +/// Check for `expr >= 0` +fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option> { + if_chain! { + if let ExprKind::Lit(ref lit) = &check.kind; + if let LitKind::Int(0, _) = &lit.node; + + then { + Some(Conversion::new_any(candidate)) + } else { + None + } + } +} + +/// Check for `expr >= (to_type::MIN as from_type)` +fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option> { + if let Some((from, to)) = get_types_from_cast(check, MIN_VALUE, SINTS) { + Conversion::try_new(candidate, from, to) + } else { + None + } +} + +/// Tries to extract the from- and to-type from a cast expression +fn get_types_from_cast<'a>(expr: &'a Expr<'_>, func: &'a str, types: &'a [&str]) -> Option<(&'a str, &'a str)> { + // `to_type::maxmin_value() as from_type` + let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! { + // to_type::maxmin_value(), from_type + if let ExprKind::Cast(ref limit, ref from_type) = &expr.kind; + if let TyKind::Path(ref from_type_path) = &from_type.kind; + if let Some(from_sym) = int_ty_to_sym(from_type_path); + + then { + Some((limit, from_sym)) + } else { + None + } + }; + + // `from_type::from(to_type::maxmin_value())` + let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { + if_chain! { + // `from_type::from, to_type::maxmin_value()` + if let ExprKind::Call(ref from_func, ref args) = &expr.kind; + // `to_type::maxmin_value()` + if args.len() == 1; + if let limit = &args[0]; + // `from_type::from` + if let ExprKind::Path(ref path) = &from_func.kind; + if let Some(from_sym) = get_implementing_type(path, INTS, FROM); + + then { + Some((limit, from_sym)) + } else { + None + } + } + }); + + if let Some((limit, from_type)) = limit_from { + if_chain! { + if let ExprKind::Call(ref fun_name, _) = &limit.kind; + // `to_type, maxmin_value` + if let ExprKind::Path(ref path) = &fun_name.kind; + // `to_type` + if let Some(to_type) = get_implementing_type(path, types, func); + + then { + Some((from_type, to_type)) + } else { + None + } + } + } else { + None + } +} + +/// Gets the type which implements the called function +fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> { + if_chain! { + if let QPath::TypeRelative(ref ty, ref path) = &path; + if path.ident.name.as_str() == function; + if let TyKind::Path(QPath::Resolved(None, ref tp)) = &ty.kind; + if let [int] = &*tp.segments; + let name = &int.ident.name.as_str(); + + then { + candidates.iter().find(|c| name == *c).cloned() + } else { + None + } + } +} + +/// Gets the type as a string, if it is a supported integer +fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> { + if_chain! { + if let QPath::Resolved(_, ref path) = *path; + if let [ty] = &*path.segments; + let name = &ty.ident.name.as_str(); + + then { + INTS.iter().find(|c| name == *c).cloned() + } else { + None + } + } +} + +/// (Option, Option) -> Option<(T, U)> +fn transpose(lhs: Option, rhs: Option) -> Option<(T, U)> { + match (lhs, rhs) { + (Some(l), Some(r)) => Some((l, r)), + _ => None, + } +} + +/// Will return the expressions as if they were expr1 <= expr2 +fn normalize_le_ge<'a>(op: &BinOp, left: &'a Expr<'a>, right: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { + match op.node { + BinOpKind::Le => Some((left, right)), + BinOpKind::Ge => Some((right, left)), + _ => None, + } +} + +// Constants +const FROM: &str = "from"; +const MAX_VALUE: &str = "max_value"; +const MIN_VALUE: &str = "min_value"; + +const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"]; +const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"]; +const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"]; diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs new file mode 100644 index 0000000000000..3ba72e84fa827 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -0,0 +1,163 @@ +//! calculate cognitive complexity and warn about overly complex functions + +use rustc_ast::ast::Attribute; +use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::BytePos; + +use crate::utils::{is_type_diagnostic_item, snippet_opt, span_lint_and_help, LimitStack}; + +declare_clippy_lint! { + /// **What it does:** Checks for methods with high cognitive complexity. + /// + /// **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. + /// + /// **Known problems:** Sometimes it's hard to find a way to reduce the + /// complexity. + /// + /// **Example:** No. You'll see it when you get the warning. + pub COGNITIVE_COMPLEXITY, + nursery, + "functions that should be split up into multiple functions" +} + +pub struct CognitiveComplexity { + limit: LimitStack, +} + +impl CognitiveComplexity { + #[must_use] + pub fn new(limit: u64) -> Self { + Self { + limit: LimitStack::new(limit), + } + } +} + +impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); + +impl CognitiveComplexity { + #[allow(clippy::cast_possible_truncation)] + fn check<'a, 'tcx>( + &mut self, + cx: &'a LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + body_span: Span, + ) { + if body_span.from_expansion() { + return; + } + + let expr = &body.value; + + let mut helper = CCHelper { cc: 1, returns: 0 }; + helper.visit_expr(expr); + let CCHelper { cc, returns } = helper; + let ret_ty = cx.tables.node_type(expr.hir_id); + let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym!(result_type)) { + returns + } else { + #[allow(clippy::integer_division)] + (returns / 2) + }; + + let mut rust_cc = cc; + // prevent degenerate cases where unreachable code contains `return` statements + if rust_cc >= ret_adjust { + rust_cc -= ret_adjust; + } + + if rust_cc > self.limit.limit() { + let fn_span = match kind { + FnKind::ItemFn(ident, _, _, _, _) | FnKind::Method(ident, _, _, _) => ident.span, + FnKind::Closure(_) => { + let header_span = body_span.with_hi(decl.output.span().lo()); + let pos = snippet_opt(cx, header_span).and_then(|snip| { + let low_offset = snip.find('|')?; + let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?; + let low = header_span.lo() + BytePos(low_offset as u32); + let high = low + BytePos(high_offset as u32 + 1); + + Some((low, high)) + }); + + if let Some((low, high)) = pos { + Span::new(low, high, header_span.ctxt()) + } else { + return; + } + }, + }; + + span_lint_and_help( + cx, + COGNITIVE_COMPLEXITY, + fn_span, + &format!( + "the function has a cognitive complexity of ({}/{})", + rust_cc, + self.limit.limit() + ), + None, + "you could split it up into multiple smaller functions", + ); + } + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CognitiveComplexity { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + let def_id = cx.tcx.hir().local_def_id(hir_id); + if !cx.tcx.has_attr(def_id.to_def_id(), sym!(test)) { + self.check(cx, kind, decl, body, span); + } + } + + fn enter_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) { + self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity"); + } + fn exit_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) { + self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity"); + } +} + +struct CCHelper { + cc: u64, + returns: u64, +} + +impl<'tcx> Visitor<'tcx> for CCHelper { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + walk_expr(self, e); + match e.kind { + ExprKind::Match(_, ref arms, _) => { + if arms.len() > 1 { + self.cc += 1; + } + self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64; + }, + ExprKind::Ret(_) => self.returns += 1, + _ => {}, + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs new file mode 100644 index 0000000000000..8090f4673aae0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs @@ -0,0 +1,170 @@ +//! Checks for if expressions that contain only an if expression. +//! +//! For example, the lint would catch: +//! +//! ```rust,ignore +//! if x { +//! if y { +//! println!("Hello world"); +//! } +//! } +//! ``` +//! +//! This lint is **warn** by default + +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::sugg::Sugg; +use crate::utils::{snippet_block, snippet_block_with_applicability, span_lint_and_sugg, span_lint_and_then}; +use rustc_errors::Applicability; + +declare_clippy_lint! { + /// **What it does:** Checks for nested `if` statements which can be collapsed + /// by `&&`-combining their conditions and for `else { if ... }` expressions + /// that + /// can be collapsed to `else if ...`. + /// + /// **Why is this bad?** Each `if`-statement adds one level of nesting, which + /// makes code look more complex than it really is. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// if x { + /// if y { + /// … + /// } + /// } + /// + /// // or + /// + /// if x { + /// … + /// } else { + /// if y { + /// … + /// } + /// } + /// ``` + /// + /// Should be written: + /// + /// ```rust.ignore + /// if x && y { + /// … + /// } + /// + /// // or + /// + /// if x { + /// … + /// } else if y { + /// … + /// } + /// ``` + pub COLLAPSIBLE_IF, + style, + "`if`s that can be collapsed (e.g., `if x { if y { ... } }` and `else { if x { ... } }`)" +} + +declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF]); + +impl EarlyLintPass for CollapsibleIf { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + if !expr.span.from_expansion() { + check_if(cx, expr) + } + } +} + +fn check_if(cx: &EarlyContext<'_>, expr: &ast::Expr) { + if let ast::ExprKind::If(check, then, else_) = &expr.kind { + if let Some(else_) = else_ { + check_collapsible_maybe_if_let(cx, else_); + } else if let ast::ExprKind::Let(..) = check.kind { + // Prevent triggering on `if let a = b { if c { .. } }`. + } else { + check_collapsible_no_if_let(cx, expr, check, then); + } + } +} + +fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool { + // We trim all opening braces and whitespaces and then check if the next string is a comment. + let trimmed_block_text = snippet_block(cx, expr.span, "..", None) + .trim_start_matches(|c: char| c.is_whitespace() || c == '{') + .to_owned(); + trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*") +} + +fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) { + if_chain! { + if let ast::ExprKind::Block(ref block, _) = else_.kind; + if !block_starts_with_comment(cx, block); + if let Some(else_) = expr_block(block); + if !else_.span.from_expansion(); + if let ast::ExprKind::If(..) = else_.kind; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + COLLAPSIBLE_IF, + block.span, + "this `else { if .. }` block can be collapsed", + "try", + snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability).into_owned(), + applicability, + ); + } + } +} + +fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) { + if_chain! { + if !block_starts_with_comment(cx, then); + if let Some(inner) = expr_block(then); + if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind; + then { + if let ast::ExprKind::Let(..) = check_inner.kind { + // Prevent triggering on `if c { if let a = b { .. } }`. + return; + } + + if expr.span.ctxt() != inner.span.ctxt() { + return; + } + span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| { + let lhs = Sugg::ast(cx, check, ".."); + let rhs = Sugg::ast(cx, check_inner, ".."); + diag.span_suggestion( + expr.span, + "try", + format!( + "if {} {}", + lhs.and(&rhs), + snippet_block(cx, content.span, "..", Some(expr.span)), + ), + Applicability::MachineApplicable, // snippet + ); + }); + } + } +} + +/// If the block contains only one expression, return it. +fn expr_block(block: &ast::Block) -> Option<&ast::Expr> { + let mut it = block.stmts.iter(); + + if let (Some(stmt), None) = (it.next(), it.next()) { + match stmt.kind { + ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => Some(expr), + _ => None, + } + } else { + None + } +} diff --git a/src/tools/clippy/clippy_lints/src/comparison_chain.rs b/src/tools/clippy/clippy_lints/src/comparison_chain.rs new file mode 100644 index 0000000000000..96df3ffe3ce67 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/comparison_chain.rs @@ -0,0 +1,118 @@ +use crate::utils::{ + get_trait_def_id, if_sequence, implements_trait, parent_node_is_if_expr, paths, span_lint_and_help, SpanlessEq, +}; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks comparison chains written with `if` that can be + /// rewritten with `match` and `cmp`. + /// + /// **Why is this bad?** `if` is not guaranteed to be exhaustive and conditionals can get + /// repetitive + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// # fn a() {} + /// # fn b() {} + /// # fn c() {} + /// fn f(x: u8, y: u8) { + /// if x > y { + /// a() + /// } else if x < y { + /// b() + /// } else { + /// c() + /// } + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust,ignore + /// use std::cmp::Ordering; + /// # fn a() {} + /// # fn b() {} + /// # fn c() {} + /// fn f(x: u8, y: u8) { + /// match x.cmp(&y) { + /// Ordering::Greater => a(), + /// Ordering::Less => b(), + /// Ordering::Equal => c() + /// } + /// } + /// ``` + pub COMPARISON_CHAIN, + style, + "`if`s that can be rewritten with `match` and `cmp`" +} + +declare_lint_pass!(ComparisonChain => [COMPARISON_CHAIN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ComparisonChain { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + // We only care about the top-most `if` in the chain + if parent_node_is_if_expr(expr, cx) { + return; + } + + // Check that there exists at least one explicit else condition + let (conds, _) = if_sequence(expr); + if conds.len() < 2 { + return; + } + + for cond in conds.windows(2) { + if let ( + &ExprKind::Binary(ref kind1, ref lhs1, ref rhs1), + &ExprKind::Binary(ref kind2, ref lhs2, ref rhs2), + ) = (&cond[0].kind, &cond[1].kind) + { + if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) { + return; + } + + // Check that both sets of operands are equal + let mut spanless_eq = SpanlessEq::new(cx); + if (!spanless_eq.eq_expr(lhs1, lhs2) || !spanless_eq.eq_expr(rhs1, rhs2)) + && (!spanless_eq.eq_expr(lhs1, rhs2) || !spanless_eq.eq_expr(rhs1, lhs2)) + { + return; + } + + // Check that the type being compared implements `core::cmp::Ord` + let ty = cx.tables.expr_ty(lhs1); + let is_ord = get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[])); + + if !is_ord { + return; + } + } else { + // We only care about comparison chains + return; + } + } + span_lint_and_help( + cx, + COMPARISON_CHAIN, + expr.span, + "`if` chain can be rewritten with `match`", + None, + "Consider rewriting the `if` chain to use `cmp` and `match`.", + ) + } +} + +fn kind_is_cmp(kind: BinOpKind) -> bool { + match kind { + BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq => true, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/consts.rs b/src/tools/clippy/clippy_lints/src/consts.rs new file mode 100644 index 0000000000000..81ddc8c0067c7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/consts.rs @@ -0,0 +1,559 @@ +#![allow(clippy::float_cmp)] + +use crate::utils::{clip, higher, sext, unsext}; +use if_chain::if_chain; +use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; +use rustc_data_structures::sync::Lrc; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::subst::{Subst, SubstsRef}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::{bug, span_bug}; +use rustc_span::symbol::Symbol; +use std::cmp::Ordering::{self, Equal}; +use std::convert::TryInto; +use std::hash::{Hash, Hasher}; + +/// A `LitKind`-like enum to fold constant `Expr`s into. +#[derive(Debug, Clone)] +pub enum Constant { + /// A `String` (e.g., "abc"). + Str(String), + /// A binary string (e.g., `b"abc"`). + Binary(Lrc>), + /// A single `char` (e.g., `'a'`). + Char(char), + /// An integer's bit representation. + Int(u128), + /// An `f32`. + F32(f32), + /// An `f64`. + F64(f64), + /// `true` or `false`. + Bool(bool), + /// An array of constants. + Vec(Vec), + /// Also an array, but with only one constant, repeated N times. + Repeat(Box, u64), + /// A tuple of constants. + Tuple(Vec), + /// A raw pointer. + RawPtr(u128), + /// A literal with syntax error. + Err(Symbol), +} + +impl PartialEq for Constant { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, + (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, + (&Self::Char(l), &Self::Char(r)) => l == r, + (&Self::Int(l), &Self::Int(r)) => l == r, + (&Self::F64(l), &Self::F64(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + l.to_bits() == r.to_bits() + }, + (&Self::F32(l), &Self::F32(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + f64::from(l).to_bits() == f64::from(r).to_bits() + }, + (&Self::Bool(l), &Self::Bool(r)) => l == r, + (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv, + // TODO: are there inter-type equalities? + _ => false, + } + } +} + +impl Hash for Constant { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + std::mem::discriminant(self).hash(state); + match *self { + Self::Str(ref s) => { + s.hash(state); + }, + Self::Binary(ref b) => { + b.hash(state); + }, + Self::Char(c) => { + c.hash(state); + }, + Self::Int(i) => { + i.hash(state); + }, + Self::F32(f) => { + f64::from(f).to_bits().hash(state); + }, + Self::F64(f) => { + f.to_bits().hash(state); + }, + Self::Bool(b) => { + b.hash(state); + }, + Self::Vec(ref v) | Self::Tuple(ref v) => { + v.hash(state); + }, + Self::Repeat(ref c, l) => { + c.hash(state); + l.hash(state); + }, + Self::RawPtr(u) => { + u.hash(state); + }, + Self::Err(ref s) => { + s.hash(state); + }, + } + } +} + +impl Constant { + pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { + match (left, right) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), + (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), + (&Self::Int(l), &Self::Int(r)) => { + if let ty::Int(int_ty) = cmp_type.kind { + Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))) + } else { + Some(l.cmp(&r)) + } + }, + (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), + (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), + (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)), + (&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l + .iter() + .zip(r.iter()) + .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri)) + .find(|r| r.map_or(true, |o| o != Ordering::Equal)) + .unwrap_or_else(|| Some(l.len().cmp(&r.len()))), + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => { + match Self::partial_cmp(tcx, cmp_type, lv, rv) { + Some(Equal) => Some(ls.cmp(rs)), + x => x, + } + }, + // TODO: are there any useful inter-type orderings? + _ => None, + } + } +} + +/// Parses a `LitKind` to a `Constant`. +pub fn lit_to_constant(lit: &LitKind, ty: Option>) -> Constant { + match *lit { + LitKind::Str(ref is, _) => Constant::Str(is.to_string()), + LitKind::Byte(b) => Constant::Int(u128::from(b)), + LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)), + LitKind::Char(c) => Constant::Char(c), + LitKind::Int(n, _) => Constant::Int(n), + LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { + FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), + FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), + }, + LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind { + ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()), + ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()), + _ => bug!(), + }, + LitKind::Bool(b) => Constant::Bool(b), + LitKind::Err(s) => Constant::Err(s), + } +} + +pub fn constant<'c, 'cc>( + lcx: &LateContext<'c, 'cc>, + tables: &'c ty::TypeckTables<'cc>, + e: &Expr<'_>, +) -> Option<(Constant, bool)> { + let mut cx = ConstEvalLateContext { + lcx, + tables, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + }; + cx.expr(e).map(|cst| (cst, cx.needed_resolution)) +} + +pub fn constant_simple<'c, 'cc>( + lcx: &LateContext<'c, 'cc>, + tables: &'c ty::TypeckTables<'cc>, + e: &Expr<'_>, +) -> Option { + constant(lcx, tables, e).and_then(|(cst, res)| if res { None } else { Some(cst) }) +} + +/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckTables`. +pub fn constant_context<'c, 'cc>( + lcx: &'c LateContext<'c, 'cc>, + tables: &'c ty::TypeckTables<'cc>, +) -> ConstEvalLateContext<'c, 'cc> { + ConstEvalLateContext { + lcx, + tables, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + } +} + +pub struct ConstEvalLateContext<'a, 'tcx> { + lcx: &'a LateContext<'a, 'tcx>, + tables: &'a ty::TypeckTables<'tcx>, + param_env: ty::ParamEnv<'tcx>, + needed_resolution: bool, + substs: SubstsRef<'tcx>, +} + +impl<'c, 'cc> ConstEvalLateContext<'c, 'cc> { + /// Simple constant folding: Insert an expression, get a constant or none. + pub fn expr(&mut self, e: &Expr<'_>) -> Option { + if let Some((ref cond, ref then, otherwise)) = higher::if_block(&e) { + return self.ifthenelse(cond, then, otherwise); + } + match e.kind { + ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.tables.expr_ty(e)), + ExprKind::Block(ref block, _) => self.block(block), + ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.tables.expr_ty_opt(e))), + ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec), + ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple), + ExprKind::Repeat(ref value, _) => { + let n = match self.tables.expr_ty(e).kind { + ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, + _ => span_bug!(e.span, "typeck error"), + }; + self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) + }, + ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op { + UnOp::UnNot => self.constant_not(&o, self.tables.expr_ty(e)), + UnOp::UnNeg => self.constant_negate(&o, self.tables.expr_ty(e)), + UnOp::UnDeref => Some(o), + }), + ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right), + ExprKind::Call(ref callee, ref args) => { + // We only handle a few const functions for now. + if_chain! { + if args.is_empty(); + if let ExprKind::Path(qpath) = &callee.kind; + let res = self.tables.qpath_res(qpath, callee.hir_id); + if let Some(def_id) = res.opt_def_id(); + let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect(); + let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect(); + if let ["core", "num", int_impl, "max_value"] = *def_path; + then { + let value = match int_impl { + "" => i8::max_value() as u128, + "" => i16::max_value() as u128, + "" => i32::max_value() as u128, + "" => i64::max_value() as u128, + "" => i128::max_value() as u128, + _ => return None, + }; + Some(Constant::Int(value)) + } + else { + None + } + } + }, + ExprKind::Index(ref arr, ref index) => self.index(arr, index), + // TODO: add other expressions. + _ => None, + } + } + + #[allow(clippy::cast_possible_wrap)] + fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { + use self::Constant::{Bool, Int}; + match *o { + Bool(b) => Some(Bool(!b)), + Int(value) => { + let value = !value; + match ty.kind { + ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))), + ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))), + _ => None, + } + }, + _ => None, + } + } + + fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { + use self::Constant::{Int, F32, F64}; + match *o { + Int(value) => { + let ity = match ty.kind { + ty::Int(ity) => ity, + _ => return None, + }; + // sign extend + let value = sext(self.lcx.tcx, value, ity); + let value = value.checked_neg()?; + // clear unused bits + Some(Int(unsext(self.lcx.tcx, value, ity))) + }, + F32(f) => Some(F32(-f)), + F64(f) => Some(F64(-f)), + _ => None, + } + } + + /// Create `Some(Vec![..])` of all constants, unless there is any + /// non-constant part. + fn multi(&mut self, vec: &[Expr<'_>]) -> Option> { + vec.iter().map(|elem| self.expr(elem)).collect::>() + } + + /// Lookup a possibly constant expression from a `ExprKind::Path`. + fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'cc>) -> Option { + let res = self.tables.qpath_res(qpath, id); + match res { + Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { + let substs = self.tables.node_substs(id); + let substs = if self.substs.is_empty() { + substs + } else { + substs.subst(self.lcx.tcx, self.substs) + }; + + let result = self + .lcx + .tcx + .const_eval_resolve(self.param_env, def_id, substs, None, None) + .ok() + .map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?; + let result = miri_to_const(&result); + if result.is_some() { + self.needed_resolution = true; + } + result + }, + // FIXME: cover all usable cases. + _ => None, + } + } + + fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { + let lhs = self.expr(lhs); + let index = self.expr(index); + + match (lhs, index) { + (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + }, + (Some(Constant::Vec(vec)), _) => { + if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { + match vec.get(0) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + } + } else { + None + } + }, + _ => None, + } + } + + /// A block can only yield a constant if it only has one constant expression. + fn block(&mut self, block: &Block<'_>) -> Option { + if block.stmts.is_empty() { + block.expr.as_ref().and_then(|b| self.expr(b)) + } else { + None + } + } + + fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { + if let Some(Constant::Bool(b)) = self.expr(cond) { + if b { + self.expr(&*then) + } else { + otherwise.as_ref().and_then(|expr| self.expr(expr)) + } + } else { + None + } + } + + fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option { + let l = self.expr(left)?; + let r = self.expr(right); + match (l, r) { + (Constant::Int(l), Some(Constant::Int(r))) => match self.tables.expr_ty(left).kind { + ty::Int(ity) => { + let l = sext(self.lcx.tcx, l, ity); + let r = sext(self.lcx.tcx, r, ity); + let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity)); + match op.node { + BinOpKind::Add => l.checked_add(r).map(zext), + BinOpKind::Sub => l.checked_sub(r).map(zext), + BinOpKind::Mul => l.checked_mul(r).map(zext), + BinOpKind::Div if r != 0 => l.checked_div(r).map(zext), + BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::BitXor => Some(zext(l ^ r)), + BinOpKind::BitOr => Some(zext(l | r)), + BinOpKind::BitAnd => Some(zext(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + } + }, + ty::Uint(_) => match op.node { + BinOpKind::Add => l.checked_add(r).map(Constant::Int), + BinOpKind::Sub => l.checked_sub(r).map(Constant::Int), + BinOpKind::Mul => l.checked_mul(r).map(Constant::Int), + BinOpKind::Div => l.checked_div(r).map(Constant::Int), + BinOpKind::Rem => l.checked_rem(r).map(Constant::Int), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::BitXor => Some(Constant::Int(l ^ r)), + BinOpKind::BitOr => Some(Constant::Int(l | r)), + BinOpKind::BitAnd => Some(Constant::Int(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + _ => None, + }, + (Constant::F32(l), Some(Constant::F32(r))) => match op.node { + BinOpKind::Add => Some(Constant::F32(l + r)), + BinOpKind::Sub => Some(Constant::F32(l - r)), + BinOpKind::Mul => Some(Constant::F32(l * r)), + BinOpKind::Div => Some(Constant::F32(l / r)), + BinOpKind::Rem => Some(Constant::F32(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (Constant::F64(l), Some(Constant::F64(r))) => match op.node { + BinOpKind::Add => Some(Constant::F64(l + r)), + BinOpKind::Sub => Some(Constant::F64(l - r)), + BinOpKind::Mul => Some(Constant::F64(l * r)), + BinOpKind::Div => Some(Constant::F64(l / r)), + BinOpKind::Rem => Some(Constant::F64(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (l, r) => match (op.node, l, r) { + (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)), + (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)), + (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => { + Some(r) + }, + (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)), + (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)), + (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)), + _ => None, + }, + } + } +} + +pub fn miri_to_const(result: &ty::Const<'_>) -> Option { + use rustc_middle::mir::interpret::{ConstValue, Scalar}; + match result.val { + ty::ConstKind::Value(ConstValue::Scalar(Scalar::Raw { data: d, .. })) => match result.ty.kind { + ty::Bool => Some(Constant::Bool(d == 1)), + ty::Uint(_) | ty::Int(_) => Some(Constant::Int(d)), + ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( + d.try_into().expect("invalid f32 bit representation"), + ))), + ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( + d.try_into().expect("invalid f64 bit representation"), + ))), + ty::RawPtr(type_and_mut) => { + if let ty::Uint(_) = type_and_mut.ty.kind { + return Some(Constant::RawPtr(d)); + } + None + }, + // FIXME: implement other conversions. + _ => None, + }, + ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind { + ty::Ref(_, tam, _) => match tam.kind { + ty::Str => String::from_utf8( + data.inspect_with_undef_and_ptr_outside_interpreter(start..end) + .to_owned(), + ) + .ok() + .map(Constant::Str), + _ => None, + }, + _ => None, + }, + ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind { + ty::Array(sub_type, len) => match sub_type.kind { + ty::Float(FloatTy::F32) => match miri_to_const(len) { + Some(Constant::Int(len)) => alloc + .inspect_with_undef_and_ptr_outside_interpreter(0..(4 * len as usize)) + .to_owned() + .chunks(4) + .map(|chunk| { + Some(Constant::F32(f32::from_le_bytes( + chunk.try_into().expect("this shouldn't happen"), + ))) + }) + .collect::>>() + .map(Constant::Vec), + _ => None, + }, + ty::Float(FloatTy::F64) => match miri_to_const(len) { + Some(Constant::Int(len)) => alloc + .inspect_with_undef_and_ptr_outside_interpreter(0..(8 * len as usize)) + .to_owned() + .chunks(8) + .map(|chunk| { + Some(Constant::F64(f64::from_le_bytes( + chunk.try_into().expect("this shouldn't happen"), + ))) + }) + .collect::>>() + .map(Constant::Vec), + _ => None, + }, + // FIXME: implement other array type conversions. + _ => None, + }, + _ => None, + }, + // FIXME: implement other conversions. + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/copies.rs b/src/tools/clippy/clippy_lints/src/copies.rs new file mode 100644 index 0000000000000..66722786eab49 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/copies.rs @@ -0,0 +1,417 @@ +use crate::utils::{get_parent_expr, higher, if_sequence, same_tys, snippet, span_lint_and_note, span_lint_and_then}; +use crate::utils::{SpanlessEq, SpanlessHash}; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{Arm, Block, Expr, ExprKind, MatchSource, Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Symbol; +use std::collections::hash_map::Entry; +use std::hash::BuildHasherDefault; + +declare_clippy_lint! { + /// **What it does:** Checks for consecutive `if`s with the same condition. + /// + /// **Why is this bad?** This is probably a copy & paste error. + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// ```ignore + /// if a == b { + /// … + /// } else if a == b { + /// … + /// } + /// ``` + /// + /// Note that this lint ignores all conditions with a function call as it could + /// have side effects: + /// + /// ```ignore + /// if foo() { + /// … + /// } else if foo() { // not linted + /// … + /// } + /// ``` + pub IFS_SAME_COND, + correctness, + "consecutive `if`s with the same condition" +} + +declare_clippy_lint! { + /// **What it does:** Checks for consecutive `if`s with the same function call. + /// + /// **Why is this bad?** This is probably a copy & paste error. + /// Despite the fact that function can have side effects and `if` works as + /// intended, such an approach is implicit and can be considered a "code smell". + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// ```ignore + /// if foo() == bar { + /// … + /// } else if foo() == bar { + /// … + /// } + /// ``` + /// + /// This probably should be: + /// ```ignore + /// if foo() == bar { + /// … + /// } else if foo() == baz { + /// … + /// } + /// ``` + /// + /// or if the original code was not a typo and called function mutates a state, + /// consider move the mutation out of the `if` condition to avoid similarity to + /// a copy & paste error: + /// + /// ```ignore + /// let first = foo(); + /// if first == bar { + /// … + /// } else { + /// let second = foo(); + /// if second == bar { + /// … + /// } + /// } + /// ``` + pub SAME_FUNCTIONS_IN_IF_CONDITION, + pedantic, + "consecutive `if`s with the same function call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `if/else` with the same body as the *then* part + /// and the *else* part. + /// + /// **Why is this bad?** This is probably a copy & paste error. + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// ```ignore + /// let foo = if … { + /// 42 + /// } else { + /// 42 + /// }; + /// ``` + pub IF_SAME_THEN_ELSE, + correctness, + "`if` with the same `then` and `else` blocks" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `match` with identical arm bodies. + /// + /// **Why is this bad?** This is probably a copy & paste error. If arm bodies + /// are the same on purpose, you can factor them + /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). + /// + /// **Known problems:** False positive possible with order dependent `match` + /// (see issue + /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). + /// + /// **Example:** + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => bar(), // <= oops + /// } + /// ``` + /// + /// This should probably be + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => baz(), // <= fixed + /// } + /// ``` + /// + /// or if the original code was not a typo: + /// ```rust,ignore + /// match foo { + /// Bar | Baz => bar(), // <= shows the intent better + /// Quz => quz(), + /// } + /// ``` + pub MATCH_SAME_ARMS, + pedantic, + "`match` with identical arm bodies" +} + +declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if !expr.span.from_expansion() { + // skip ifs directly in else, it will be checked in the parent if + if let Some(expr) = get_parent_expr(cx, expr) { + if let Some((_, _, Some(ref else_expr))) = higher::if_block(&expr) { + if else_expr.hir_id == expr.hir_id { + return; + } + } + } + + let (conds, blocks) = if_sequence(expr); + lint_same_then_else(cx, &blocks); + lint_same_cond(cx, &conds); + lint_same_fns_in_if_cond(cx, &conds); + lint_match_arms(cx, expr); + } + } +} + +/// Implementation of `IF_SAME_THEN_ELSE`. +fn lint_same_then_else(cx: &LateContext<'_, '_>, blocks: &[&Block<'_>]) { + let eq: &dyn Fn(&&Block<'_>, &&Block<'_>) -> bool = + &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) }; + + if let Some((i, j)) = search_same_sequenced(blocks, eq) { + span_lint_and_note( + cx, + IF_SAME_THEN_ELSE, + j.span, + "this `if` has identical blocks", + Some(i.span), + "same as this", + ); + } +} + +/// Implementation of `IFS_SAME_COND`. +fn lint_same_cond(cx: &LateContext<'_, '_>, conds: &[&Expr<'_>]) { + let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 { + let mut h = SpanlessHash::new(cx, cx.tables); + h.hash_expr(expr); + h.finish() + }; + + let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = + &|&lhs, &rhs| -> bool { SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) }; + + for (i, j) in search_same(conds, hash, eq) { + span_lint_and_note( + cx, + IFS_SAME_COND, + j.span, + "this `if` has the same condition as a previous `if`", + Some(i.span), + "same as this", + ); + } +} + +/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. +fn lint_same_fns_in_if_cond(cx: &LateContext<'_, '_>, conds: &[&Expr<'_>]) { + let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 { + let mut h = SpanlessHash::new(cx, cx.tables); + h.hash_expr(expr); + h.finish() + }; + + let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { + // Do not spawn warning if `IFS_SAME_COND` already produced it. + if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) { + return false; + } + SpanlessEq::new(cx).eq_expr(lhs, rhs) + }; + + for (i, j) in search_same(conds, hash, eq) { + span_lint_and_note( + cx, + SAME_FUNCTIONS_IN_IF_CONDITION, + j.span, + "this `if` has the same function call as a previous `if`", + Some(i.span), + "same as this", + ); + } +} + +/// Implementation of `MATCH_SAME_ARMS`. +fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr<'_>) { + fn same_bindings<'tcx>( + cx: &LateContext<'_, 'tcx>, + lhs: &FxHashMap>, + rhs: &FxHashMap>, + ) -> bool { + lhs.len() == rhs.len() + && lhs + .iter() + .all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| same_tys(cx, l_ty, r_ty))) + } + + if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { + let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { + let mut h = SpanlessHash::new(cx, cx.tables); + h.hash_expr(&arm.body); + h.finish() + }; + + let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { + let min_index = usize::min(lindex, rindex); + let max_index = usize::max(lindex, rindex); + + // Arms with a guard are ignored, those can’t always be merged together + // This is also the case for arms in-between each there is an arm with a guard + (min_index..=max_index).all(|index| arms[index].guard.is_none()) && + SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) && + // all patterns should have the same bindings + same_bindings(cx, &bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat)) + }; + + let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); + for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + j.body.span, + "this `match` has identical arm bodies", + |diag| { + diag.span_note(i.body.span, "same as this"); + + // Note: this does not use `span_suggestion` on purpose: + // there is no clean way + // to remove the other arm. Building a span and suggest to replace it to "" + // makes an even more confusing error message. Also in order not to make up a + // span for the whole pattern, the suggestion is only shown when there is only + // one pattern. The user should know about `|` if they are already using it… + + let lhs = snippet(cx, i.pat.span, ""); + let rhs = snippet(cx, j.pat.span, ""); + + if let PatKind::Wild = j.pat.kind { + // if the last arm is _, then i could be integrated into _ + // note that i.pat cannot be _, because that would mean that we're + // hiding all the subsequent arms, and rust won't compile + diag.span_note( + i.body.span, + &format!( + "`{}` has the same arm body as the `_` wildcard, consider removing it", + lhs + ), + ); + } else { + diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs)); + } + }, + ); + } + } +} + +/// Returns the list of bindings in a pattern. +fn bindings<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat<'_>) -> FxHashMap> { + fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat<'_>, map: &mut FxHashMap>) { + match pat.kind { + PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map), + PatKind::TupleStruct(_, pats, _) => { + for pat in pats { + bindings_impl(cx, pat, map); + } + }, + PatKind::Binding(.., ident, ref as_pat) => { + if let Entry::Vacant(v) = map.entry(ident.name) { + v.insert(cx.tables.pat_ty(pat)); + } + if let Some(ref as_pat) = *as_pat { + bindings_impl(cx, as_pat, map); + } + }, + PatKind::Or(fields) | PatKind::Tuple(fields, _) => { + for pat in fields { + bindings_impl(cx, pat, map); + } + }, + PatKind::Struct(_, fields, _) => { + for pat in fields { + bindings_impl(cx, &pat.pat, map); + } + }, + PatKind::Slice(lhs, ref mid, rhs) => { + for pat in lhs { + bindings_impl(cx, pat, map); + } + if let Some(ref mid) = *mid { + bindings_impl(cx, mid, map); + } + for pat in rhs { + bindings_impl(cx, pat, map); + } + }, + PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (), + } + } + + let mut result = FxHashMap::default(); + bindings_impl(cx, pat, &mut result); + result +} + +fn search_same_sequenced(exprs: &[T], eq: Eq) -> Option<(&T, &T)> +where + Eq: Fn(&T, &T) -> bool, +{ + for win in exprs.windows(2) { + if eq(&win[0], &win[1]) { + return Some((&win[0], &win[1])); + } + } + None +} + +fn search_common_cases<'a, T, Eq>(exprs: &'a [T], eq: &Eq) -> Option<(&'a T, &'a T)> +where + Eq: Fn(&T, &T) -> bool, +{ + if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) { + Some((&exprs[0], &exprs[1])) + } else { + None + } +} + +fn search_same(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)> +where + Hash: Fn(&T) -> u64, + Eq: Fn(&T, &T) -> bool, +{ + if let Some(expr) = search_common_cases(&exprs, &eq) { + return vec![expr]; + } + + let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); + + let mut map: FxHashMap<_, Vec<&_>> = + FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); + + for expr in exprs { + match map.entry(hash(expr)) { + Entry::Occupied(mut o) => { + for o in o.get() { + if eq(o, expr) { + match_expr_list.push((o, expr)); + } + } + o.get_mut().push(expr); + }, + Entry::Vacant(v) => { + v.insert(vec![expr]); + }, + } + } + + match_expr_list +} diff --git a/src/tools/clippy/clippy_lints/src/copy_iterator.rs b/src/tools/clippy/clippy_lints/src/copy_iterator.rs new file mode 100644 index 0000000000000..d79aa2ef0209e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/copy_iterator.rs @@ -0,0 +1,55 @@ +use crate::utils::{is_copy, match_path, paths, span_lint_and_note}; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for types that implement `Copy` as well as + /// `Iterator`. + /// + /// **Why is this bad?** Implicit copies can be confusing when working with + /// iterator combinators. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// #[derive(Copy, Clone)] + /// struct Countdown(u8); + /// + /// impl Iterator for Countdown { + /// // ... + /// } + /// + /// let a: Vec<_> = my_iterator.take(1).collect(); + /// let b: Vec<_> = my_iterator.collect(); + /// ``` + pub COPY_ITERATOR, + pedantic, + "implementing `Iterator` on a `Copy` type" +} + +declare_lint_pass!(CopyIterator => [COPY_ITERATOR]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyIterator { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl { + of_trait: Some(ref trait_ref), + .. + } = item.kind + { + let ty = cx.tcx.type_of(cx.tcx.hir().local_def_id(item.hir_id)); + + if is_copy(cx, ty) && match_path(&trait_ref.path, &paths::ITERATOR) { + span_lint_and_note( + cx, + COPY_ITERATOR, + item.span, + "you are implementing `Iterator` on a `Copy` type", + None, + "consider implementing `IntoIterator` instead", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs new file mode 100644 index 0000000000000..e513dcce64e53 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs @@ -0,0 +1,65 @@ +use crate::utils::{snippet_opt, span_lint_and_help, span_lint_and_sugg}; +use rustc_ast::ast; +use rustc_ast::tokenstream::TokenStream; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of dbg!() macro. + /// + /// **Why is this bad?** `dbg!` macro is intended as a debugging tool. It + /// should not be in version control. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// dbg!(true) + /// + /// // Good + /// true + /// ``` + pub DBG_MACRO, + restriction, + "`dbg!` macro is intended as a debugging tool" +} + +declare_lint_pass!(DbgMacro => [DBG_MACRO]); + +impl EarlyLintPass for DbgMacro { + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { + if mac.path == sym!(dbg) { + if let Some(sugg) = tts_span(mac.args.inner_tokens()).and_then(|span| snippet_opt(cx, span)) { + span_lint_and_sugg( + cx, + DBG_MACRO, + mac.span(), + "`dbg!` macro is intended as a debugging tool", + "ensure to avoid having uses of it in version control", + sugg, + Applicability::MaybeIncorrect, + ); + } else { + span_lint_and_help( + cx, + DBG_MACRO, + mac.span(), + "`dbg!` macro is intended as a debugging tool", + None, + "ensure to avoid having uses of it in version control", + ); + } + } + } +} + +// Get span enclosing entire the token stream. +fn tts_span(tts: TokenStream) -> Option { + let mut cursor = tts.into_trees(); + let first = cursor.next()?.span(); + let span = cursor.last().map_or(first, |tree| first.to(tree.span())); + Some(span) +} diff --git a/src/tools/clippy/clippy_lints/src/default_trait_access.rs b/src/tools/clippy/clippy_lints/src/default_trait_access.rs new file mode 100644 index 0000000000000..635d609c38289 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/default_trait_access.rs @@ -0,0 +1,76 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{any_parent_is_automatically_derived, match_def_path, paths, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for literal calls to `Default::default()`. + /// + /// **Why is this bad?** It's more clear to the reader to use the name of the type whose default is + /// being gotten than the generic `Default`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let s: String = Default::default(); + /// + /// // Good + /// let s = String::default(); + /// ``` + pub DEFAULT_TRAIT_ACCESS, + pedantic, + "checks for literal calls to `Default::default()`" +} + +declare_lint_pass!(DefaultTraitAccess => [DEFAULT_TRAIT_ACCESS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DefaultTraitAccess { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref path, ..) = expr.kind; + if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); + if let ExprKind::Path(ref qpath) = path.kind; + if let Some(def_id) = cx.tables.qpath_res(qpath, path.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD); + then { + match qpath { + QPath::Resolved(..) => { + if_chain! { + // Detect and ignore ::default() because these calls do + // explicitly name the type. + if let ExprKind::Call(ref method, ref _args) = expr.kind; + if let ExprKind::Path(ref p) = method.kind; + if let QPath::Resolved(Some(_ty), _path) = p; + then { + return; + } + } + + // TODO: Work out a way to put "whatever the imported way of referencing + // this type in this file" rather than a fully-qualified type. + let expr_ty = cx.tables.expr_ty(expr); + if let ty::Adt(..) = expr_ty.kind { + let replacement = format!("{}::default()", expr_ty); + span_lint_and_sugg( + cx, + DEFAULT_TRAIT_ACCESS, + expr.span, + &format!("Calling `{}` is more clear than this expression", replacement), + "try", + replacement, + Applicability::Unspecified, // First resolve the TODO above + ); + } + }, + QPath::TypeRelative(..) => {}, + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs new file mode 100644 index 0000000000000..6e8ca647dd7ae --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs @@ -0,0 +1,157 @@ +macro_rules! declare_deprecated_lint { + (pub $name: ident, $_reason: expr) => { + declare_lint!(pub $name, Allow, "deprecated lint") + } +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This used to check for `assert!(a == b)` and recommend + /// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011. + pub SHOULD_ASSERT_EQ, + "`assert!()` will be more flexible with RFC 2011" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This used to check for `Vec::extend`, which was slower than + /// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true. + pub EXTEND_FROM_SLICE, + "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** `Range::step_by(0)` used to be linted since it's + /// an infinite iterator, which is better expressed by `iter::repeat`, + /// but the method has been removed for `Iterator::step_by` which panics + /// if given a zero + pub RANGE_STEP_BY_ZERO, + "`iterator.step_by(0)` panics nowadays" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This used to check for `Vec::as_slice`, which was unstable with good + /// stable alternatives. `Vec::as_slice` has now been stabilized. + pub UNSTABLE_AS_SLICE, + "`Vec::as_slice` has been stabilized in 1.7" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This used to check for `Vec::as_mut_slice`, which was unstable with good + /// stable alternatives. `Vec::as_mut_slice` has now been stabilized. + pub UNSTABLE_AS_MUT_SLICE, + "`Vec::as_mut_slice` has been stabilized in 1.7" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This used to check for `.to_string()` method calls on values + /// of type `&str`. This is not unidiomatic and with specialization coming, `to_string` could be + /// specialized to be as efficient as `to_owned`. + pub STR_TO_STRING, + "using `str::to_string` is common even today and specialization will likely happen soon" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This used to check for `.to_string()` method calls on values + /// of type `String`. This is not unidiomatic and with specialization coming, `to_string` could be + /// specialized to be as efficient as `clone`. + pub STRING_TO_STRING, + "using `string::to_string` is common even today and specialization will likely happen soon" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint should never have applied to non-pointer types, as transmuting + /// between non-pointer types of differing alignment is well-defined behavior (it's semantically + /// equivalent to a memcpy). This lint has thus been refactored into two separate lints: + /// cast_ptr_alignment and transmute_ptr_to_ptr. + pub MISALIGNED_TRANSMUTE, + "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint is too subjective, not having a good reason for being in clippy. + /// Additionally, compound assignment operators may be overloaded separately from their non-assigning + /// counterparts, so this lint may suggest a change in behavior or the code may not compile. + pub ASSIGN_OPS, + "using compound assignment operators (e.g., `+=`) is harmless" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** The original rule will only lint for `if let`. After + /// making it support to lint `match`, naming as `if let` is not suitable for it. + /// So, this lint is deprecated. + pub IF_LET_REDUNDANT_PATTERN_MATCHING, + "this lint has been changed to redundant_pattern_matching" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint used to suggest replacing `let mut vec = + /// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The + /// replacement has very different performance characteristics so the lint is + /// deprecated. + pub UNSAFE_VECTOR_INITIALIZATION, + "the replacement suggested by this lint had substantially different behavior" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been superseded by the warn-by-default + /// `invalid_value` rustc lint. + pub INVALID_REF, + "superseded by rustc lint `invalid_value`" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been superseded by #[must_use] in rustc. + pub UNUSED_COLLECT, + "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been uplifted to rustc and is now called + /// `array_into_iter`. + pub INTO_ITER_ON_ARRAY, + "this lint has been uplifted to rustc and is now called `array_into_iter`" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been uplifted to rustc and is now called + /// `unused_labels`. + pub UNUSED_LABEL, + "this lint has been uplifted to rustc and is now called `unused_labels`" +} + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** Associated-constants are now preferred. + pub REPLACE_CONSTS, + "associated-constants `MIN`/`MAX` of integers are prefer to `{min,max}_value()` and module constants" +} diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs new file mode 100644 index 0000000000000..68ec07e2bcb0f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -0,0 +1,113 @@ +use crate::utils::{get_parent_expr, implements_trait, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX, PREC_PREFIX}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for explicit `deref()` or `deref_mut()` method calls. + /// + /// **Why is this bad?** Derefencing by `&*x` or `&mut *x` is clearer and more concise, + /// when not part of a method chain. + /// + /// **Example:** + /// ```rust + /// use std::ops::Deref; + /// let a: &mut String = &mut String::from("foo"); + /// let b: &str = a.deref(); + /// ``` + /// Could be written as: + /// ```rust + /// let a: &mut String = &mut String::from("foo"); + /// let b = &*a; + /// ``` + /// + /// This lint excludes + /// ```rust,ignore + /// let _ = d.unwrap().deref(); + /// ``` + pub EXPLICIT_DEREF_METHODS, + pedantic, + "Explicit use of deref or deref_mut method while not in a method chain." +} + +declare_lint_pass!(Dereferencing => [ + EXPLICIT_DEREF_METHODS +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Dereferencing { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if !expr.span.from_expansion(); + if let ExprKind::MethodCall(ref method_name, _, ref args) = &expr.kind; + if args.len() == 1; + + then { + if let Some(parent_expr) = get_parent_expr(cx, expr) { + // Check if we have the whole call chain here + if let ExprKind::MethodCall(..) = parent_expr.kind { + return; + } + // Check for Expr that we don't want to be linted + let precedence = parent_expr.precedence(); + match precedence { + // Lint a Call is ok though + ExprPrecedence::Call | ExprPrecedence::AddrOf => (), + _ => { + if precedence.order() >= PREC_PREFIX && precedence.order() <= PREC_POSTFIX { + return; + } + } + } + } + let name = method_name.ident.as_str(); + lint_deref(cx, &*name, &args[0], args[0].span, expr.span); + } + } + } +} + +fn lint_deref(cx: &LateContext<'_, '_>, method_name: &str, call_expr: &Expr<'_>, var_span: Span, expr_span: Span) { + match method_name { + "deref" => { + if cx + .tcx + .lang_items() + .deref_trait() + .map_or(false, |id| implements_trait(cx, cx.tables.expr_ty(&call_expr), id, &[])) + { + span_lint_and_sugg( + cx, + EXPLICIT_DEREF_METHODS, + expr_span, + "explicit deref method call", + "try this", + format!("&*{}", &snippet(cx, var_span, "..")), + Applicability::MachineApplicable, + ); + } + }, + "deref_mut" => { + if cx + .tcx + .lang_items() + .deref_mut_trait() + .map_or(false, |id| implements_trait(cx, cx.tables.expr_ty(&call_expr), id, &[])) + { + span_lint_and_sugg( + cx, + EXPLICIT_DEREF_METHODS, + expr_span, + "explicit deref_mut method call", + "try this", + format!("&mut *{}", &snippet(cx, var_span, "..")), + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } +} diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs new file mode 100644 index 0000000000000..3cbb8fa72f74f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/derive.rs @@ -0,0 +1,309 @@ +use crate::utils::paths; +use crate::utils::{ + is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{ + BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, TraitRef, UnsafeSource, Unsafety, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq` + /// explicitly or vice versa. + /// + /// **Why is this bad?** The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + pub DERIVE_HASH_XOR_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + +declare_clippy_lint! { + /// **What it does:** Checks for explicit `Clone` implementations for `Copy` + /// types. + /// + /// **Why is this bad?** To avoid surprising behaviour, these traits should + /// agree and the behaviour of `Copy` cannot be overridden. In almost all + /// situations a `Copy` type should have a `Clone` implementation that does + /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]` + /// gets you. + /// + /// **Known problems:** Bounds of generic types are sometimes wrong: https://github.com/rust-lang/rust/issues/26925 + /// + /// **Example:** + /// ```rust,ignore + /// #[derive(Copy)] + /// struct Foo; + /// + /// impl Clone for Foo { + /// // .. + /// } + /// ``` + pub EXPL_IMPL_CLONE_ON_COPY, + pedantic, + "implementing `Clone` explicitly on `Copy` types" +} + +declare_clippy_lint! { + /// **What it does:** Checks for deriving `serde::Deserialize` on a type that + /// has methods using `unsafe`. + /// + /// **Why is this bad?** Deriving `serde::Deserialize` will create a constructor + /// that may violate invariants hold by another constructor. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// use serde::Deserialize; + /// + /// #[derive(Deserialize)] + /// pub struct Foo { + /// // .. + /// } + /// + /// impl Foo { + /// pub fn new() -> Self { + /// // setup here .. + /// } + /// + /// pub unsafe fn parts() -> (&str, &str) { + /// // assumes invariants hold + /// } + /// } + /// ``` + pub UNSAFE_DERIVE_DESERIALIZE, + pedantic, + "deriving `serde::Deserialize` on a type that has methods using `unsafe`" +} + +declare_lint_pass!(Derive => [EXPL_IMPL_CLONE_ON_COPY, DERIVE_HASH_XOR_EQ, UNSAFE_DERIVE_DESERIALIZE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Derive { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl { + of_trait: Some(ref trait_ref), + .. + } = item.kind + { + let ty = cx.tcx.type_of(cx.tcx.hir().local_def_id(item.hir_id)); + let is_automatically_derived = is_automatically_derived(&*item.attrs); + + check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); + + if is_automatically_derived { + check_unsafe_derive_deserialize(cx, item, trait_ref, ty); + } else { + check_copy_clone(cx, item, trait_ref, ty); + } + } + } +} + +/// Implementation of the `DERIVE_HASH_XOR_EQ` lint. +fn check_hash_peq<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + span: Span, + trait_ref: &TraitRef<'_>, + ty: Ty<'tcx>, + hash_is_automatically_derived: bool, +) { + if_chain! { + if match_path(&trait_ref.path, &paths::HASH); + if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); + if let Some(def_id) = &trait_ref.trait_def_id(); + if !def_id.is_local(); + then { + // Look for the PartialEq implementations for `ty` + cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { + let peq_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id)); + + if peq_is_automatically_derived == hash_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); + + // Only care about `impl PartialEq for Foo` + // For `impl PartialEq for A, input_types is [A, B] + if trait_ref.substs.type_at(1) == ty { + let mess = if peq_is_automatically_derived { + "you are implementing `Hash` explicitly but have derived `PartialEq`" + } else { + "you are deriving `Hash` but have implemented `PartialEq` explicitly" + }; + + span_lint_and_then( + cx, + DERIVE_HASH_XOR_EQ, + span, + mess, + |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.hir().as_local_hir_id(local_def_id); + diag.span_note( + cx.tcx.hir().span(hir_id), + "`PartialEq` implemented here" + ); + } + } + ); + } + }); + } + } +} + +/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. +fn check_copy_clone<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) { + if match_path(&trait_ref.path, &paths::CLONE_TRAIT) { + if !is_copy(cx, ty) { + return; + } + + match ty.kind { + ty::Adt(def, _) if def.is_union() => return, + + // Some types are not Clone by default but could be cloned “by hand” if necessary + ty::Adt(def, substs) => { + for variant in &def.variants { + for field in &variant.fields { + if let ty::FnDef(..) = field.ty(cx.tcx, substs).kind { + return; + } + } + for subst in substs { + if let ty::subst::GenericArgKind::Type(subst) = subst.unpack() { + if let ty::Param(_) = subst.kind { + return; + } + } + } + } + }, + _ => (), + } + + span_lint_and_note( + cx, + EXPL_IMPL_CLONE_ON_COPY, + item.span, + "you are implementing `Clone` explicitly on a `Copy` type", + Some(item.span), + "consider deriving `Clone` or removing `Copy`", + ); + } +} + +/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. +fn check_unsafe_derive_deserialize<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + item: &Item<'_>, + trait_ref: &TraitRef<'_>, + ty: Ty<'tcx>, +) { + fn item_from_def_id<'tcx>(cx: &LateContext<'_, 'tcx>, def_id: DefId) -> &'tcx Item<'tcx> { + let hir_id = cx.tcx.hir().as_local_hir_id(def_id.expect_local()); + cx.tcx.hir().expect_item(hir_id) + } + + fn has_unsafe<'tcx>(cx: &LateContext<'_, 'tcx>, item: &'tcx Item<'_>) -> bool { + let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; + walk_item(&mut visitor, item); + visitor.has_unsafe + } + + if_chain! { + if match_path(&trait_ref.path, &paths::SERDE_DESERIALIZE); + if let ty::Adt(def, _) = ty.kind; + if def.did.is_local(); + if cx.tcx.inherent_impls(def.did) + .iter() + .map(|imp_did| item_from_def_id(cx, *imp_did)) + .any(|imp| has_unsafe(cx, imp)); + then { + span_lint_and_help( + cx, + UNSAFE_DERIVE_DESERIALIZE, + item.span, + "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", + None, + "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html" + ); + } + } +} + +struct UnsafeVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + has_unsafe: bool, +} + +impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) { + if self.has_unsafe { + return; + } + + if_chain! { + if let Some(header) = kind.header(); + if let Unsafety::Unsafe = header.unsafety; + then { + self.has_unsafe = true; + } + } + + walk_fn(self, kind, decl, body_id, span, id); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.has_unsafe { + return; + } + + if let ExprKind::Block(block, _) = expr.kind { + match block.rules { + BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + | BlockCheckMode::PushUnsafeBlock(UnsafeSource::UserProvided) + | BlockCheckMode::PopUnsafeBlock(UnsafeSource::UserProvided) => { + self.has_unsafe = true; + }, + _ => {}, + } + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs new file mode 100644 index 0000000000000..8d1e91f9adbd6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/doc.rs @@ -0,0 +1,525 @@ +use crate::utils::{implements_trait, is_entrypoint_fn, is_type_diagnostic_item, return_ty, span_lint}; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_ast::ast::{AttrKind, Attribute}; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{BytePos, MultiSpan, Span}; +use rustc_span::Pos; +use std::ops::Range; +use url::Url; + +declare_clippy_lint! { + /// **What it does:** Checks for the presence of `_`, `::` or camel-case words + /// outside ticks in documentation. + /// + /// **Why is this bad?** *Rustdoc* supports markdown formatting, `_`, `::` and + /// camel-case probably indicates some code which should be included between + /// ticks. `_` can also be used for emphasis in markdown, this lint tries to + /// consider that. + /// + /// **Known problems:** Lots of bad docs won’t be fixed, what the lint checks + /// for is limited, and there are still false positives. + /// + /// **Examples:** + /// ```rust + /// /// Do something with the foo_bar parameter. See also + /// /// that::other::module::foo. + /// // ^ `foo_bar` and `that::other::module::foo` should be ticked. + /// fn doit(foo_bar: usize) {} + /// ``` + pub DOC_MARKDOWN, + pedantic, + "presence of `_`, `::` or camel-case outside backticks in documentation" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the doc comments of publicly visible + /// unsafe functions and warns if there is no `# Safety` section. + /// + /// **Why is this bad?** Unsafe functions should document their safety + /// preconditions, so that users can be sure they are using them safely. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + ///# type Universe = (); + /// /// This function should really be documented + /// pub unsafe fn start_apocalypse(u: &mut Universe) { + /// unimplemented!(); + /// } + /// ``` + /// + /// At least write a line about safety: + /// + /// ```rust + ///# type Universe = (); + /// /// # Safety + /// /// + /// /// This function should not be called before the horsemen are ready. + /// pub unsafe fn start_apocalypse(u: &mut Universe) { + /// unimplemented!(); + /// } + /// ``` + pub MISSING_SAFETY_DOC, + style, + "`pub unsafe fn` without `# Safety` docs" +} + +declare_clippy_lint! { + /// **What it does:** Checks the doc comments of publicly visible functions that + /// return a `Result` type and warns if there is no `# Errors` section. + /// + /// **Why is this bad?** Documenting the type of errors that can be returned from a + /// function can help callers write code to handle the errors appropriately. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// + /// Since the following function returns a `Result` it has an `# Errors` section in + /// its doc comment: + /// + /// ```rust + ///# use std::io; + /// /// # Errors + /// /// + /// /// Will return `Err` if `filename` does not exist or the user does not have + /// /// permission to read it. + /// pub fn read(filename: String) -> io::Result { + /// unimplemented!(); + /// } + /// ``` + pub MISSING_ERRORS_DOC, + pedantic, + "`pub fn` returns `Result` without `# Errors` in doc comment" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `fn main() { .. }` in doctests + /// + /// **Why is this bad?** The test can be shorter (and likely more readable) + /// if the `fn main()` is left implicit. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ``````rust + /// /// An example of a doctest with a `main()` function + /// /// + /// /// # Examples + /// /// + /// /// ``` + /// /// fn main() { + /// /// // this needs not be in an `fn` + /// /// } + /// /// ``` + /// fn needless_main() { + /// unimplemented!(); + /// } + /// `````` + pub NEEDLESS_DOCTEST_MAIN, + style, + "presence of `fn main() {` in code examples" +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Clone)] +pub struct DocMarkdown { + valid_idents: FxHashSet, + in_trait_impl: bool, +} + +impl DocMarkdown { + pub fn new(valid_idents: FxHashSet) -> Self { + Self { + valid_idents, + in_trait_impl: false, + } + } +} + +impl_lint_pass!(DocMarkdown => [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, NEEDLESS_DOCTEST_MAIN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DocMarkdown { + fn check_crate(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx hir::Crate<'_>) { + check_attrs(cx, &self.valid_idents, &krate.item.attrs); + } + + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + let headers = check_attrs(cx, &self.valid_idents, &item.attrs); + match item.kind { + hir::ItemKind::Fn(ref sig, _, body_id) => { + if !(is_entrypoint_fn(cx, cx.tcx.hir().local_def_id(item.hir_id).to_def_id()) + || in_external_macro(cx.tcx.sess, item.span)) + { + lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id)); + } + }, + hir::ItemKind::Impl { + of_trait: ref trait_ref, + .. + } => { + self.in_trait_impl = trait_ref.is_some(); + }, + _ => {}, + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + if let hir::ItemKind::Impl { .. } = item.kind { + self.in_trait_impl = false; + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'_>) { + let headers = check_attrs(cx, &self.valid_idents, &item.attrs); + if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind { + if !in_external_macro(cx.tcx.sess, item.span) { + lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, None); + } + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'_>) { + let headers = check_attrs(cx, &self.valid_idents, &item.attrs); + if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) { + return; + } + if let hir::ImplItemKind::Fn(ref sig, body_id) = item.kind { + lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id)); + } + } +} + +fn lint_for_missing_headers<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + hir_id: hir::HirId, + span: impl Into + Copy, + sig: &hir::FnSig<'_>, + headers: DocHeaders, + body_id: Option, +) { + if !cx.access_levels.is_exported(hir_id) { + return; // Private functions do not require doc comments + } + if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe { + span_lint( + cx, + MISSING_SAFETY_DOC, + span, + "unsafe function's docs miss `# Safety` section", + ); + } + if !headers.errors { + if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym!(result_type)) { + span_lint( + cx, + MISSING_ERRORS_DOC, + span, + "docs for function returning `Result` missing `# Errors` section", + ); + } else { + if_chain! { + if let Some(body_id) = body_id; + if let Some(future) = cx.tcx.lang_items().future_trait(); + let def_id = cx.tcx.hir().body_owner_def_id(body_id); + let mir = cx.tcx.optimized_mir(def_id.to_def_id()); + let ret_ty = mir.return_ty(); + if implements_trait(cx, ret_ty, future, &[]); + if let ty::Opaque(_, subs) = ret_ty.kind; + if let Some(gen) = subs.types().next(); + if let ty::Generator(_, subs, _) = gen.kind; + if is_type_diagnostic_item(cx, subs.as_generator().return_ty(), sym!(result_type)); + then { + span_lint( + cx, + MISSING_ERRORS_DOC, + span, + "docs for function returning `Result` missing `# Errors` section", + ); + } + } + } + } +} + +/// Cleanup documentation decoration (`///` and such). +/// +/// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or +/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we +/// need to keep track of +/// the spans but this function is inspired from the later. +#[allow(clippy::cast_possible_truncation)] +#[must_use] +pub fn strip_doc_comment_decoration(comment: &str, span: Span) -> (String, Vec<(usize, Span)>) { + // one-line comments lose their prefix + const ONELINERS: &[&str] = &["///!", "///", "//!", "//"]; + for prefix in ONELINERS { + if comment.starts_with(*prefix) { + let doc = &comment[prefix.len()..]; + let mut doc = doc.to_owned(); + doc.push('\n'); + return ( + doc.to_owned(), + vec![(doc.len(), span.with_lo(span.lo() + BytePos(prefix.len() as u32)))], + ); + } + } + + if comment.starts_with("/*") { + let doc = &comment[3..comment.len() - 2]; + let mut sizes = vec![]; + let mut contains_initial_stars = false; + for line in doc.lines() { + let offset = line.as_ptr() as usize - comment.as_ptr() as usize; + debug_assert_eq!(offset as u32 as usize, offset); + contains_initial_stars |= line.trim_start().starts_with('*'); + // +1 for the newline + sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(offset as u32)))); + } + if !contains_initial_stars { + return (doc.to_string(), sizes); + } + // remove the initial '*'s if any + let mut no_stars = String::with_capacity(doc.len()); + for line in doc.lines() { + let mut chars = line.chars(); + while let Some(c) = chars.next() { + if c.is_whitespace() { + no_stars.push(c); + } else { + no_stars.push(if c == '*' { ' ' } else { c }); + break; + } + } + no_stars.push_str(chars.as_str()); + no_stars.push('\n'); + } + return (no_stars, sizes); + } + + panic!("not a doc-comment: {}", comment); +} + +#[derive(Copy, Clone)] +struct DocHeaders { + safety: bool, + errors: bool, +} + +fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet, attrs: &'a [Attribute]) -> DocHeaders { + let mut doc = String::new(); + let mut spans = vec![]; + + for attr in attrs { + if let AttrKind::DocComment(ref comment) = attr.kind { + let comment = comment.to_string(); + let (comment, current_spans) = strip_doc_comment_decoration(&comment, attr.span); + spans.extend_from_slice(¤t_spans); + doc.push_str(&comment); + } else if attr.check_name(sym!(doc)) { + // ignore mix of sugared and non-sugared doc + // don't trigger the safety or errors check + return DocHeaders { + safety: true, + errors: true, + }; + } + } + + let mut current = 0; + for &mut (ref mut offset, _) in &mut spans { + let offset_copy = *offset; + *offset = current; + current += offset_copy; + } + + if doc.is_empty() { + return DocHeaders { + safety: false, + errors: false, + }; + } + + let parser = pulldown_cmark::Parser::new(&doc).into_offset_iter(); + // Iterate over all `Events` and combine consecutive events into one + let events = parser.coalesce(|previous, current| { + use pulldown_cmark::Event::Text; + + let previous_range = previous.1; + let current_range = current.1; + + match (previous.0, current.0) { + (Text(previous), Text(current)) => { + let mut previous = previous.to_string(); + previous.push_str(¤t); + Ok((Text(previous.into()), previous_range)) + }, + (previous, current) => Err(((previous, previous_range), (current, current_range))), + } + }); + check_doc(cx, valid_idents, events, &spans) +} + +const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail", "edition2018"]; + +fn check_doc<'a, Events: Iterator, Range)>>( + cx: &LateContext<'_, '_>, + valid_idents: &FxHashSet, + events: Events, + spans: &[(usize, Span)], +) -> DocHeaders { + // true if a safety header was found + use pulldown_cmark::CodeBlockKind; + use pulldown_cmark::Event::{ + Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, + }; + use pulldown_cmark::Tag::{CodeBlock, Heading, Link}; + + let mut headers = DocHeaders { + safety: false, + errors: false, + }; + let mut in_code = false; + let mut in_link = None; + let mut in_heading = false; + let mut is_rust = false; + for (event, range) in events { + match event { + Start(CodeBlock(ref kind)) => { + in_code = true; + if let CodeBlockKind::Fenced(lang) = kind { + is_rust = + lang.is_empty() || !lang.contains("ignore") && lang.split(',').any(|i| RUST_CODE.contains(&i)); + } + }, + End(CodeBlock(_)) => { + in_code = false; + is_rust = false; + }, + Start(Link(_, url, _)) => in_link = Some(url), + End(Link(..)) => in_link = None, + Start(Heading(_)) => in_heading = true, + End(Heading(_)) => in_heading = false, + Start(_tag) | End(_tag) => (), // We don't care about other tags + Html(_html) => (), // HTML is weird, just ignore it + SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (), + FootnoteReference(text) | Text(text) => { + if Some(&text) == in_link.as_ref() { + // Probably a link of the form `` + // Which are represented as a link to "http://example.com" with + // text "http://example.com" by pulldown-cmark + continue; + } + headers.safety |= in_heading && text.trim() == "Safety"; + headers.errors |= in_heading && text.trim() == "Errors"; + let index = match spans.binary_search_by(|c| c.0.cmp(&range.start)) { + Ok(o) => o, + Err(e) => e - 1, + }; + let (begin, span) = spans[index]; + if in_code { + if is_rust { + check_code(cx, &text, span); + } + } else { + // Adjust for the beginning of the current `Event` + let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin)); + + check_text(cx, valid_idents, &text, span); + } + }, + } + } + headers +} + +static LEAVE_MAIN_PATTERNS: &[&str] = &["static", "fn main() {}", "extern crate", "async fn main() {"]; + +fn check_code(cx: &LateContext<'_, '_>, text: &str, span: Span) { + if text.contains("fn main() {") && !LEAVE_MAIN_PATTERNS.iter().any(|p| text.contains(p)) { + span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest"); + } +} + +fn check_text(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet, text: &str, span: Span) { + for word in text.split(|c: char| c.is_whitespace() || c == '\'') { + // Trim punctuation as in `some comment (see foo::bar).` + // ^^ + // Or even as in `_foo bar_` which is emphasized. + let word = word.trim_matches(|c: char| !c.is_alphanumeric()); + + if valid_idents.contains(word) { + continue; + } + + // Adjust for the current word + let offset = word.as_ptr() as usize - text.as_ptr() as usize; + let span = Span::new( + span.lo() + BytePos::from_usize(offset), + span.lo() + BytePos::from_usize(offset + word.len()), + span.ctxt(), + ); + + check_word(cx, word, span); + } +} + +fn check_word(cx: &LateContext<'_, '_>, word: &str, span: Span) { + /// Checks if a string is camel-case, i.e., contains at least two uppercase + /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok). + /// Plurals are also excluded (`IDs` is ok). + fn is_camel_case(s: &str) -> bool { + if s.starts_with(|c: char| c.is_digit(10)) { + return false; + } + + let s = if s.ends_with('s') { &s[..s.len() - 1] } else { s }; + + s.chars().all(char::is_alphanumeric) + && s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1 + && s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0 + } + + fn has_underscore(s: &str) -> bool { + s != "_" && !s.contains("\\_") && s.contains('_') + } + + fn has_hyphen(s: &str) -> bool { + s != "-" && s.contains('-') + } + + if let Ok(url) = Url::parse(word) { + // try to get around the fact that `foo::bar` parses as a valid URL + if !url.cannot_be_a_base() { + span_lint( + cx, + DOC_MARKDOWN, + span, + "you should put bare URLs between `<`/`>` or make a proper Markdown link", + ); + + return; + } + } + + // We assume that mixed-case words are not meant to be put inside bacticks. (Issue #2343) + if has_underscore(word) && has_hyphen(word) { + return; + } + + if has_underscore(word) || word.contains("::") || is_camel_case(word) { + span_lint( + cx, + DOC_MARKDOWN, + span, + &format!("you should put `{}` between ticks in the documentation", word), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/double_comparison.rs b/src/tools/clippy/clippy_lints/src/double_comparison.rs new file mode 100644 index 0000000000000..44f85d1ea6e19 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/double_comparison.rs @@ -0,0 +1,95 @@ +//! Lint on unnecessary double comparisons. Some examples: + +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +use crate::utils::{snippet_with_applicability, span_lint_and_sugg, SpanlessEq}; + +declare_clippy_lint! { + /// **What it does:** Checks for double comparisons that could be simplified to a single expression. + /// + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// # let y = 2; + /// if x == y || x < y {} + /// ``` + /// + /// Could be written as: + /// + /// ```rust + /// # let x = 1; + /// # let y = 2; + /// if x <= y {} + /// ``` + pub DOUBLE_COMPARISONS, + complexity, + "unnecessary double comparisons that can be simplified" +} + +declare_lint_pass!(DoubleComparisons => [DOUBLE_COMPARISONS]); + +impl<'a, 'tcx> DoubleComparisons { + #[allow(clippy::similar_names)] + fn check_binop(cx: &LateContext<'a, 'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) { + let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) { + (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => { + (lb.node, llhs, lrhs, rb.node, rlhs, rrhs) + }, + _ => return, + }; + let mut spanless_eq = SpanlessEq::new(cx).ignore_fn(); + if !(spanless_eq.eq_expr(&llhs, &rlhs) && spanless_eq.eq_expr(&lrhs, &rrhs)) { + return; + } + macro_rules! lint_double_comparison { + ($op:tt) => {{ + let mut applicability = Applicability::MachineApplicable; + let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); + let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); + let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str); + span_lint_and_sugg( + cx, + DOUBLE_COMPARISONS, + span, + "This binary expression can be simplified", + "try", + sugg, + applicability, + ); + }}; + } + #[rustfmt::skip] + match (op, lkind, rkind) { + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { + lint_double_comparison!(<=) + }, + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { + lint_double_comparison!(>=) + }, + (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { + lint_double_comparison!(!=) + }, + (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { + lint_double_comparison!(==) + }, + _ => (), + }; + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DoubleComparisons { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = expr.kind { + Self::check_binop(cx, kind.node, lhs, rhs, expr.span); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/double_parens.rs b/src/tools/clippy/clippy_lints/src/double_parens.rs new file mode 100644 index 0000000000000..7f2ff8b9b26f6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/double_parens.rs @@ -0,0 +1,75 @@ +use crate::utils::span_lint; +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for unnecessary double parentheses. + /// + /// **Why is this bad?** This makes code harder to read and might indicate a + /// mistake. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo(bar: usize) {} + /// ((0)); + /// foo((0)); + /// ((1, 2)); + /// ``` + pub DOUBLE_PARENS, + complexity, + "Warn on unnecessary double parentheses" +} + +declare_lint_pass!(DoubleParens => [DOUBLE_PARENS]); + +impl EarlyLintPass for DoubleParens { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if expr.span.from_expansion() { + return; + } + + match expr.kind { + ExprKind::Paren(ref in_paren) => match in_paren.kind { + ExprKind::Paren(_) | ExprKind::Tup(_) => { + span_lint( + cx, + DOUBLE_PARENS, + expr.span, + "Consider removing unnecessary double parentheses", + ); + }, + _ => {}, + }, + ExprKind::Call(_, ref params) => { + if params.len() == 1 { + let param = ¶ms[0]; + if let ExprKind::Paren(_) = param.kind { + span_lint( + cx, + DOUBLE_PARENS, + param.span, + "Consider removing unnecessary double parentheses", + ); + } + } + }, + ExprKind::MethodCall(_, ref params) => { + if params.len() == 2 { + let param = ¶ms[1]; + if let ExprKind::Paren(_) = param.kind { + span_lint( + cx, + DOUBLE_PARENS, + param.span, + "Consider removing unnecessary double parentheses", + ); + } + } + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/drop_bounds.rs b/src/tools/clippy/clippy_lints/src/drop_bounds.rs new file mode 100644 index 0000000000000..f496680827913 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/drop_bounds.rs @@ -0,0 +1,69 @@ +use crate::utils::{match_def_path, paths, span_lint}; +use if_chain::if_chain; +use rustc_hir::{GenericBound, GenericParam, WhereBoundPredicate, WherePredicate}; +use rustc_lint::LateLintPass; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for generics with `std::ops::Drop` as bounds. + /// + /// **Why is this bad?** `Drop` bounds do not really accomplish anything. + /// A type may have compiler-generated drop glue without implementing the + /// `Drop` trait itself. The `Drop` trait also only has one method, + /// `Drop::drop`, and that function is by fiat not callable in user code. + /// So there is really no use case for using `Drop` in trait bounds. + /// + /// The most likely use case of a drop bound is to distinguish between types + /// that have destructors and types that don't. Combined with specialization, + /// a naive coder would write an implementation that assumed a type could be + /// trivially dropped, then write a specialization for `T: Drop` that actually + /// calls the destructor. Except that doing so is not correct; String, for + /// example, doesn't actually implement Drop, but because String contains a + /// Vec, assuming it can be trivially dropped will leak memory. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo() {} + /// ``` + pub DROP_BOUNDS, + correctness, + "Bounds of the form `T: Drop` are useless" +} + +const DROP_BOUNDS_SUMMARY: &str = "Bounds of the form `T: Drop` are useless. \ + Use `std::mem::needs_drop` to detect if a type has drop glue."; + +declare_lint_pass!(DropBounds => [DROP_BOUNDS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DropBounds { + fn check_generic_param(&mut self, cx: &rustc_lint::LateContext<'a, 'tcx>, p: &'tcx GenericParam<'_>) { + for bound in p.bounds.iter() { + lint_bound(cx, bound); + } + } + fn check_where_predicate(&mut self, cx: &rustc_lint::LateContext<'a, 'tcx>, p: &'tcx WherePredicate<'_>) { + if let WherePredicate::BoundPredicate(WhereBoundPredicate { bounds, .. }) = p { + for bound in *bounds { + lint_bound(cx, bound); + } + } + } +} + +fn lint_bound<'a, 'tcx>(cx: &rustc_lint::LateContext<'a, 'tcx>, bound: &'tcx GenericBound<'_>) { + if_chain! { + if let GenericBound::Trait(t, _) = bound; + if let Some(def_id) = t.trait_ref.path.res.opt_def_id(); + if match_def_path(cx, def_id, &paths::DROP_TRAIT); + then { + span_lint( + cx, + DROP_BOUNDS, + t.span, + DROP_BOUNDS_SUMMARY + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/drop_forget_ref.rs b/src/tools/clippy/clippy_lints/src/drop_forget_ref.rs new file mode 100644 index 0000000000000..9de9056c14029 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/drop_forget_ref.rs @@ -0,0 +1,160 @@ +use crate::utils::{is_copy, match_def_path, paths, qpath_res, span_lint_and_note}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `std::mem::drop` with a reference + /// instead of an owned value. + /// + /// **Why is this bad?** Calling `drop` on a reference will only drop the + /// reference itself, which is a no-op. It will not call the `drop` method (from + /// the `Drop` trait implementation) on the underlying referenced value, which + /// is likely what was intended. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// let mut lock_guard = mutex.lock(); + /// std::mem::drop(&lock_guard) // Should have been drop(lock_guard), mutex + /// // still locked + /// operation_that_requires_mutex_to_be_unlocked(); + /// ``` + pub DROP_REF, + correctness, + "calls to `std::mem::drop` with a reference instead of an owned value" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `std::mem::forget` with a reference + /// instead of an owned value. + /// + /// **Why is this bad?** Calling `forget` on a reference will only forget the + /// reference itself, which is a no-op. It will not forget the underlying + /// referenced + /// value, which is likely what was intended. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = Box::new(1); + /// std::mem::forget(&x) // Should have been forget(x), x will still be dropped + /// ``` + pub FORGET_REF, + correctness, + "calls to `std::mem::forget` with a reference instead of an owned value" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `std::mem::drop` with a value + /// that derives the Copy trait + /// + /// **Why is this bad?** Calling `std::mem::drop` [does nothing for types that + /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html), since the + /// value will be copied and moved into the function on invocation. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: i32 = 42; // i32 implements Copy + /// std::mem::drop(x) // A copy of x is passed to the function, leaving the + /// // original unaffected + /// ``` + pub DROP_COPY, + correctness, + "calls to `std::mem::drop` with a value that implements Copy" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `std::mem::forget` with a value that + /// derives the Copy trait + /// + /// **Why is this bad?** Calling `std::mem::forget` [does nothing for types that + /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html) since the + /// value will be copied and moved into the function on invocation. + /// + /// An alternative, but also valid, explanation is that Copy types do not + /// implement + /// the Drop trait, which means they have no destructors. Without a destructor, + /// there + /// is nothing for `std::mem::forget` to ignore. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: i32 = 42; // i32 implements Copy + /// std::mem::forget(x) // A copy of x is passed to the function, leaving the + /// // original unaffected + /// ``` + pub FORGET_COPY, + correctness, + "calls to `std::mem::forget` with a value that implements Copy" +} + +const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \ + Dropping a reference does nothing."; +const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \ + Forgetting a reference does nothing."; +const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that implements `Copy`. \ + Dropping a copy leaves the original intact."; +const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \ + Forgetting a copy leaves the original intact."; + +declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COPY]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DropForgetRef { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref path, ref args) = expr.kind; + if let ExprKind::Path(ref qpath) = path.kind; + if args.len() == 1; + if let Some(def_id) = qpath_res(cx, qpath, path.hir_id).opt_def_id(); + then { + let lint; + let msg; + let arg = &args[0]; + let arg_ty = cx.tables.expr_ty(arg); + + if let ty::Ref(..) = arg_ty.kind { + if match_def_path(cx, def_id, &paths::DROP) { + lint = DROP_REF; + msg = DROP_REF_SUMMARY.to_string(); + } else if match_def_path(cx, def_id, &paths::MEM_FORGET) { + lint = FORGET_REF; + msg = FORGET_REF_SUMMARY.to_string(); + } else { + return; + } + span_lint_and_note(cx, + lint, + expr.span, + &msg, + Some(arg.span), + &format!("argument has type `{}`", arg_ty)); + } else if is_copy(cx, arg_ty) { + if match_def_path(cx, def_id, &paths::DROP) { + lint = DROP_COPY; + msg = DROP_COPY_SUMMARY.to_string(); + } else if match_def_path(cx, def_id, &paths::MEM_FORGET) { + lint = FORGET_COPY; + msg = FORGET_COPY_SUMMARY.to_string(); + } else { + return; + } + span_lint_and_note(cx, + lint, + expr.span, + &msg, + Some(arg.span), + &format!("argument has type {}", arg_ty)); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/duration_subsec.rs b/src/tools/clippy/clippy_lints/src/duration_subsec.rs new file mode 100644 index 0000000000000..b35a8facf8b99 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/duration_subsec.rs @@ -0,0 +1,65 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +use crate::consts::{constant, Constant}; +use crate::utils::paths; +use crate::utils::{match_type, snippet_with_applicability, span_lint_and_sugg, walk_ptrs_ty}; + +declare_clippy_lint! { + /// **What it does:** Checks for calculation of subsecond microseconds or milliseconds + /// from other `Duration` methods. + /// + /// **Why is this bad?** It's more concise to call `Duration::subsec_micros()` or + /// `Duration::subsec_millis()` than to calculate them. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::time::Duration; + /// let dur = Duration::new(5, 0); + /// let _micros = dur.subsec_nanos() / 1_000; + /// let _millis = dur.subsec_nanos() / 1_000_000; + /// ``` + pub DURATION_SUBSEC, + complexity, + "checks for calculation of subsecond microseconds or milliseconds" +} + +declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DurationSubsec { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, ref left, ref right) = expr.kind; + if let ExprKind::MethodCall(ref method_path, _ , ref args) = left.kind; + if match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(&args[0])), &paths::DURATION); + if let Some((Constant::Int(divisor), _)) = constant(cx, cx.tables, right); + then { + let suggested_fn = match (method_path.ident.as_str().as_ref(), divisor) { + ("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis", + ("subsec_nanos", 1_000) => "subsec_micros", + _ => return, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + DURATION_SUBSEC, + expr.span, + &format!("Calling `{}()` is more concise than this calculation", suggested_fn), + "try", + format!( + "{}.{}()", + snippet_with_applicability(cx, args[0].span, "_", &mut applicability), + suggested_fn + ), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/else_if_without_else.rs b/src/tools/clippy/clippy_lints/src/else_if_without_else.rs new file mode 100644 index 0000000000000..95123e6ff6fe2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/else_if_without_else.rs @@ -0,0 +1,72 @@ +//! Lint on if expressions with an else if, but without a final else branch. + +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of if expressions with an `else if` branch, + /// but without a final `else` branch. + /// + /// **Why is this bad?** Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn a() {} + /// # fn b() {} + /// # let x: i32 = 1; + /// if x.is_positive() { + /// a(); + /// } else if x.is_negative() { + /// b(); + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # fn a() {} + /// # fn b() {} + /// # let x: i32 = 1; + /// if x.is_positive() { + /// a(); + /// } else if x.is_negative() { + /// b(); + /// } else { + /// // We don't care about zero. + /// } + /// ``` + pub ELSE_IF_WITHOUT_ELSE, + restriction, + "`if` expression with an `else if`, but without a final `else` branch" +} + +declare_lint_pass!(ElseIfWithoutElse => [ELSE_IF_WITHOUT_ELSE]); + +impl EarlyLintPass for ElseIfWithoutElse { + fn check_expr(&mut self, cx: &EarlyContext<'_>, mut item: &Expr) { + if in_external_macro(cx.sess(), item.span) { + return; + } + + while let ExprKind::If(_, _, Some(ref els)) = item.kind { + if let ExprKind::If(_, _, None) = els.kind { + span_lint_and_help( + cx, + ELSE_IF_WITHOUT_ELSE, + els.span, + "`if` expression with an `else if`, but without a final `else`", + None, + "add an `else` block here", + ); + } + + item = els; + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/empty_enum.rs b/src/tools/clippy/clippy_lints/src/empty_enum.rs new file mode 100644 index 0000000000000..3bfef6f4bed12 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/empty_enum.rs @@ -0,0 +1,60 @@ +//! lint when there is an enum with no variants + +use crate::utils::span_lint_and_help; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `enum`s with no variants. + /// + /// **Why is this bad?** If you want to introduce a type which + /// can't be instantiated, you should use `!` (the never type), + /// or a wrapper around it, because `!` has more extensive + /// compiler support (type inference, etc...) and wrappers + /// around it are the conventional way to define an uninhabited type. + /// For further information visit [never type documentation](https://doc.rust-lang.org/std/primitive.never.html) + /// + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust + /// enum Test {} + /// ``` + /// + /// Good: + /// ```rust + /// #![feature(never_type)] + /// + /// struct Test(!); + /// ``` + pub EMPTY_ENUM, + pedantic, + "enum with no variants" +} + +declare_lint_pass!(EmptyEnum => [EMPTY_ENUM]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for EmptyEnum { + fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item<'_>) { + let did = cx.tcx.hir().local_def_id(item.hir_id); + if let ItemKind::Enum(..) = item.kind { + let ty = cx.tcx.type_of(did); + let adt = ty.ty_adt_def().expect("already checked whether this is an enum"); + if adt.variants.is_empty() { + span_lint_and_help( + cx, + EMPTY_ENUM, + item.span, + "enum with no variants", + None, + "consider using the uninhabited type `!` (never type) or a wrapper \ + around it to introduce a type which can't be instantiated", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/entry.rs b/src/tools/clippy/clippy_lints/src/entry.rs new file mode 100644 index 0000000000000..7b332c761a0c4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/entry.rs @@ -0,0 +1,187 @@ +use crate::utils::SpanlessEq; +use crate::utils::{get_item_name, higher, is_type_diagnostic_item, match_type, paths, snippet, snippet_opt}; +use crate::utils::{snippet_with_applicability, span_lint_and_then, walk_ptrs_ty}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BorrowKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for uses of `contains_key` + `insert` on `HashMap` + /// or `BTreeMap`. + /// + /// **Why is this bad?** Using `entry` is more efficient. + /// + /// **Known problems:** Some false negatives, eg.: + /// ```rust + /// # use std::collections::HashMap; + /// # let mut map = HashMap::new(); + /// # let v = 1; + /// # let k = 1; + /// if !map.contains_key(&k) { + /// map.insert(k.clone(), v); + /// } + /// ``` + /// + /// **Example:** + /// ```rust + /// # use std::collections::HashMap; + /// # let mut map = HashMap::new(); + /// # let k = 1; + /// # let v = 1; + /// if !map.contains_key(&k) { + /// map.insert(k, v); + /// } + /// ``` + /// can both be rewritten as: + /// ```rust + /// # use std::collections::HashMap; + /// # let mut map = HashMap::new(); + /// # let k = 1; + /// # let v = 1; + /// map.entry(k).or_insert(v); + /// ``` + pub MAP_ENTRY, + perf, + "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`" +} + +declare_lint_pass!(HashMapPass => [MAP_ENTRY]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for HashMapPass { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let Some((ref check, ref then_block, ref else_block)) = higher::if_block(&expr) { + if let ExprKind::Unary(UnOp::UnNot, ref check) = check.kind { + if let Some((ty, map, key)) = check_cond(cx, check) { + // in case of `if !m.contains_key(&k) { m.insert(k, v); }` + // we can give a better error message + let sole_expr = { + else_block.is_none() + && if let ExprKind::Block(ref then_block, _) = then_block.kind { + (then_block.expr.is_some() as usize) + then_block.stmts.len() == 1 + } else { + true + } + // XXXManishearth we can also check for if/else blocks containing `None`. + }; + + let mut visitor = InsertVisitor { + cx, + span: expr.span, + ty, + map, + key, + sole_expr, + }; + + walk_expr(&mut visitor, &**then_block); + } + } else if let Some(ref else_block) = *else_block { + if let Some((ty, map, key)) = check_cond(cx, check) { + let mut visitor = InsertVisitor { + cx, + span: expr.span, + ty, + map, + key, + sole_expr: false, + }; + + walk_expr(&mut visitor, else_block); + } + } + } + } +} + +fn check_cond<'a, 'tcx, 'b>( + cx: &'a LateContext<'a, 'tcx>, + check: &'b Expr<'b>, +) -> Option<(&'static str, &'b Expr<'b>, &'b Expr<'b>)> { + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref params) = check.kind; + if params.len() >= 2; + if path.ident.name == sym!(contains_key); + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref key) = params[1].kind; + then { + let map = ¶ms[0]; + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(map)); + + return if match_type(cx, obj_ty, &paths::BTREEMAP) { + Some(("BTreeMap", map, key)) + } + else if is_type_diagnostic_item(cx, obj_ty, sym!(hashmap_type)) { + Some(("HashMap", map, key)) + } + else { + None + }; + } + } + + None +} + +struct InsertVisitor<'a, 'tcx, 'b> { + cx: &'a LateContext<'a, 'tcx>, + span: Span, + ty: &'static str, + map: &'b Expr<'b>, + key: &'b Expr<'b>, + sole_expr: bool, +} + +impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref params) = expr.kind; + if params.len() == 3; + if path.ident.name == sym!(insert); + if get_item_name(self.cx, self.map) == get_item_name(self.cx, ¶ms[0]); + if SpanlessEq::new(self.cx).eq_expr(self.key, ¶ms[1]); + if snippet_opt(self.cx, self.map.span) == snippet_opt(self.cx, params[0].span); + then { + span_lint_and_then(self.cx, MAP_ENTRY, self.span, + &format!("usage of `contains_key` followed by `insert` on a `{}`", self.ty), |diag| { + if self.sole_expr { + let mut app = Applicability::MachineApplicable; + let help = format!("{}.entry({}).or_insert({});", + snippet_with_applicability(self.cx, self.map.span, "map", &mut app), + snippet_with_applicability(self.cx, params[1].span, "..", &mut app), + snippet_with_applicability(self.cx, params[2].span, "..", &mut app)); + + diag.span_suggestion( + self.span, + "consider using", + help, + Applicability::MachineApplicable, // snippet + ); + } + else { + let help = format!("consider using `{}.entry({})`", + snippet(self.cx, self.map.span, "map"), + snippet(self.cx, params[1].span, "..")); + + diag.span_label( + self.span, + &help, + ); + } + }); + } + } + + if !self.sole_expr { + walk_expr(self, expr); + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/enum_clike.rs b/src/tools/clippy/clippy_lints/src/enum_clike.rs new file mode 100644 index 0000000000000..a1fed3fb6e205 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/enum_clike.rs @@ -0,0 +1,82 @@ +//! lint on C-like enums that are `repr(isize/usize)` and have values that +//! don't fit into an `i32` + +use crate::consts::{miri_to_const, Constant}; +use crate::utils::span_lint; +use rustc_ast::ast::{IntTy, UintTy}; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::util::IntTypeExt; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::convert::TryFrom; + +declare_clippy_lint! { + /// **What it does:** Checks for C-like enumerations that are + /// `repr(isize/usize)` and have values that don't fit into an `i32`. + /// + /// **Why is this bad?** This will truncate the variant value on 32 bit + /// architectures, but works fine on 64 bit. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # #[cfg(target_pointer_width = "64")] + /// #[repr(usize)] + /// enum NonPortable { + /// X = 0x1_0000_0000, + /// Y = 0, + /// } + /// ``` + pub ENUM_CLIKE_UNPORTABLE_VARIANT, + correctness, + "C-like enums that are `repr(isize/usize)` and have values that don't fit into an `i32`" +} + +declare_lint_pass!(UnportableVariant => [ENUM_CLIKE_UNPORTABLE_VARIANT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnportableVariant { + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss)] + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if cx.tcx.data_layout.pointer_size.bits() != 64 { + return; + } + if let ItemKind::Enum(def, _) = &item.kind { + for var in def.variants { + if let Some(anon_const) = &var.disr_expr { + let def_id = cx.tcx.hir().body_owner_def_id(anon_const.body); + let mut ty = cx.tcx.type_of(def_id.to_def_id()); + let constant = cx + .tcx + .const_eval_poly(def_id.to_def_id()) + .ok() + .map(|val| rustc_middle::ty::Const::from_value(cx.tcx, val, ty)); + if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) { + if let ty::Adt(adt, _) = ty.kind { + if adt.is_enum() { + ty = adt.repr.discr_type().to_ty(cx.tcx); + } + } + match ty.kind { + ty::Int(IntTy::Isize) => { + let val = ((val as i128) << 64) >> 64; + if i32::try_from(val).is_ok() { + continue; + } + }, + ty::Uint(UintTy::Usize) if val > u128::from(u32::max_value()) => {}, + _ => continue, + } + span_lint( + cx, + ENUM_CLIKE_UNPORTABLE_VARIANT, + var.span, + "Clike enum variant discriminant is not portable to 32-bit targets", + ); + }; + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/enum_variants.rs b/src/tools/clippy/clippy_lints/src/enum_variants.rs new file mode 100644 index 0000000000000..a5871cf0cd4dd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/enum_variants.rs @@ -0,0 +1,305 @@ +//! lint on enum variants that are prefixed or suffixed by the same characters + +use crate::utils::{camel_case, is_present_in_source}; +use crate::utils::{span_lint, span_lint_and_help}; +use rustc_ast::ast::{EnumDef, Item, ItemKind, VisibilityKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; + +declare_clippy_lint! { + /// **What it does:** Detects enumeration variants that are prefixed or suffixed + /// by the same characters. + /// + /// **Why is this bad?** Enumeration variant names should specify their variant, + /// not repeat the enumeration name. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// enum Cake { + /// BlackForestCake, + /// HummingbirdCake, + /// BattenbergCake, + /// } + /// ``` + pub ENUM_VARIANT_NAMES, + style, + "enums where all variants share a prefix/postfix" +} + +declare_clippy_lint! { + /// **What it does:** Detects enumeration variants that are prefixed or suffixed + /// by the same characters. + /// + /// **Why is this bad?** Enumeration variant names should specify their variant, + /// not repeat the enumeration name. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// enum Cake { + /// BlackForestCake, + /// HummingbirdCake, + /// BattenbergCake, + /// } + /// ``` + pub PUB_ENUM_VARIANT_NAMES, + pedantic, + "enums where all variants share a prefix/postfix" +} + +declare_clippy_lint! { + /// **What it does:** Detects type names that are prefixed or suffixed by the + /// containing module's name. + /// + /// **Why is this bad?** It requires the user to type the module name twice. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// mod cake { + /// struct BlackForestCake; + /// } + /// ``` + pub MODULE_NAME_REPETITIONS, + pedantic, + "type names prefixed/postfixed with their containing module's name" +} + +declare_clippy_lint! { + /// **What it does:** Checks for modules that have the same name as their + /// parent module + /// + /// **Why is this bad?** A typical beginner mistake is to have `mod foo;` and + /// again `mod foo { .. + /// }` in `foo.rs`. + /// The expectation is that items inside the inner `mod foo { .. }` are then + /// available + /// through `foo::x`, but they are only available through + /// `foo::foo::x`. + /// If this is done on purpose, it would be better to choose a more + /// representative module name. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// // lib.rs + /// mod foo; + /// // foo.rs + /// mod foo { + /// ... + /// } + /// ``` + pub MODULE_INCEPTION, + style, + "modules that have the same name as their parent module" +} + +pub struct EnumVariantNames { + modules: Vec<(Symbol, String)>, + threshold: u64, +} + +impl EnumVariantNames { + #[must_use] + pub fn new(threshold: u64) -> Self { + Self { + modules: Vec::new(), + threshold, + } + } +} + +impl_lint_pass!(EnumVariantNames => [ + ENUM_VARIANT_NAMES, + PUB_ENUM_VARIANT_NAMES, + MODULE_NAME_REPETITIONS, + MODULE_INCEPTION +]); + +/// Returns the number of chars that match from the start +#[must_use] +fn partial_match(pre: &str, name: &str) -> usize { + let mut name_iter = name.chars(); + let _ = name_iter.next_back(); // make sure the name is never fully matched + pre.chars().zip(name_iter).take_while(|&(l, r)| l == r).count() +} + +/// Returns the number of chars that match from the end +#[must_use] +fn partial_rmatch(post: &str, name: &str) -> usize { + let mut name_iter = name.chars(); + let _ = name_iter.next(); // make sure the name is never fully matched + post.chars() + .rev() + .zip(name_iter.rev()) + .take_while(|&(l, r)| l == r) + .count() +} + +fn check_variant( + cx: &EarlyContext<'_>, + threshold: u64, + def: &EnumDef, + item_name: &str, + item_name_chars: usize, + span: Span, + lint: &'static Lint, +) { + if (def.variants.len() as u64) < threshold { + return; + } + for var in &def.variants { + let name = var.ident.name.as_str(); + if partial_match(item_name, &name) == item_name_chars + && name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase()) + && name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric()) + { + span_lint(cx, lint, var.span, "Variant name starts with the enum's name"); + } + if partial_rmatch(item_name, &name) == item_name_chars { + span_lint(cx, lint, var.span, "Variant name ends with the enum's name"); + } + } + let first = &def.variants[0].ident.name.as_str(); + let mut pre = &first[..camel_case::until(&*first)]; + let mut post = &first[camel_case::from(&*first)..]; + for var in &def.variants { + let name = var.ident.name.as_str(); + + let pre_match = partial_match(pre, &name); + pre = &pre[..pre_match]; + let pre_camel = camel_case::until(pre); + pre = &pre[..pre_camel]; + while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() { + if next.is_numeric() { + return; + } + if next.is_lowercase() { + let last = pre.len() - last.len_utf8(); + let last_camel = camel_case::until(&pre[..last]); + pre = &pre[..last_camel]; + } else { + break; + } + } + + let post_match = partial_rmatch(post, &name); + let post_end = post.len() - post_match; + post = &post[post_end..]; + let post_camel = camel_case::from(post); + post = &post[post_camel..]; + } + let (what, value) = match (pre.is_empty(), post.is_empty()) { + (true, true) => return, + (false, _) => ("pre", pre), + (true, false) => ("post", post), + }; + span_lint_and_help( + cx, + lint, + span, + &format!("All variants have the same {}fix: `{}`", what, value), + None, + &format!( + "remove the {}fixes and use full paths to \ + the variants instead of glob imports", + what + ), + ); +} + +#[must_use] +fn to_camel_case(item_name: &str) -> String { + let mut s = String::new(); + let mut up = true; + for c in item_name.chars() { + if c.is_uppercase() { + // we only turn snake case text into CamelCase + return item_name.to_string(); + } + if c == '_' { + up = true; + continue; + } + if up { + up = false; + s.extend(c.to_uppercase()); + } else { + s.push(c); + } + } + s +} + +impl EarlyLintPass for EnumVariantNames { + fn check_item_post(&mut self, _cx: &EarlyContext<'_>, _item: &Item) { + let last = self.modules.pop(); + assert!(last.is_some()); + } + + #[allow(clippy::similar_names)] + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + let item_name = item.ident.name.as_str(); + let item_name_chars = item_name.chars().count(); + let item_camel = to_camel_case(&item_name); + if !item.span.from_expansion() && is_present_in_source(cx, item.span) { + if let Some(&(ref mod_name, ref mod_camel)) = self.modules.last() { + // constants don't have surrounding modules + if !mod_camel.is_empty() { + if mod_name == &item.ident.name { + if let ItemKind::Mod(..) = item.kind { + span_lint( + cx, + MODULE_INCEPTION, + item.span, + "module has the same name as its containing module", + ); + } + } + if item.vis.node.is_pub() { + let matching = partial_match(mod_camel, &item_camel); + let rmatching = partial_rmatch(mod_camel, &item_camel); + let nchars = mod_camel.chars().count(); + + let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric(); + + if matching == nchars { + match item_camel.chars().nth(nchars) { + Some(c) if is_word_beginning(c) => span_lint( + cx, + MODULE_NAME_REPETITIONS, + item.span, + "item name starts with its containing module's name", + ), + _ => (), + } + } + if rmatching == nchars { + span_lint( + cx, + MODULE_NAME_REPETITIONS, + item.span, + "item name ends with its containing module's name", + ); + } + } + } + } + } + if let ItemKind::Enum(ref def, _) = item.kind { + let lint = match item.vis.node { + VisibilityKind::Public => PUB_ENUM_VARIANT_NAMES, + _ => ENUM_VARIANT_NAMES, + }; + check_variant(cx, self.threshold, def, &item_name, item_name_chars, item.span, lint); + } + self.modules.push((item.ident.name, item_camel)); + } +} diff --git a/src/tools/clippy/clippy_lints/src/eq_op.rs b/src/tools/clippy/clippy_lints/src/eq_op.rs new file mode 100644 index 0000000000000..098d47bdd40cb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/eq_op.rs @@ -0,0 +1,229 @@ +use crate::utils::{ + implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then, SpanlessEq, +}; +use rustc_errors::Applicability; +use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for equal operands to comparison, logical and + /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`, + /// `||`, `&`, `|`, `^`, `-` and `/`). + /// + /// **Why is this bad?** This is usually just a typo or a copy and paste error. + /// + /// **Known problems:** False negatives: We had some false positives regarding + /// calls (notably [racer](https://github.com/phildawes/racer) had one instance + /// of `x.pop() && x.pop()`), so we removed matching any function or method + /// calls. We may introduce a whitelist of known pure functions in the future. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// if x + 1 == x + 1 {} + /// ``` + pub EQ_OP, + correctness, + "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)" +} + +declare_clippy_lint! { + /// **What it does:** Checks for arguments to `==` which have their address + /// taken to satisfy a bound + /// and suggests to dereference the other argument instead + /// + /// **Why is this bad?** It is more idiomatic to dereference the other argument. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```ignore + /// &x == y + /// ``` + pub OP_REF, + style, + "taking a reference to satisfy the type constraints on `==`" +} + +declare_lint_pass!(EqOp => [EQ_OP, OP_REF]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for EqOp { + #[allow(clippy::similar_names, clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Binary(op, ref left, ref right) = e.kind { + if e.span.from_expansion() { + return; + } + let macro_with_not_op = |expr_kind: &ExprKind<'_>| { + if let ExprKind::Unary(_, ref expr) = *expr_kind { + in_macro(expr.span) + } else { + false + } + }; + if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) { + return; + } + if is_valid_operator(op) && SpanlessEq::new(cx).ignore_fn().eq_expr(left, right) { + span_lint( + cx, + EQ_OP, + e.span, + &format!("equal expressions as operands to `{}`", op.node.as_str()), + ); + return; + } + let (trait_id, requires_ref) = match op.node { + BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false), + BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false), + BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false), + BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false), + BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false), + // don't lint short circuiting ops + BinOpKind::And | BinOpKind::Or => return, + BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false), + BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false), + BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false), + BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false), + BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false), + BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true), + BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => { + (cx.tcx.lang_items().partial_ord_trait(), true) + }, + }; + if let Some(trait_id) = trait_id { + #[allow(clippy::match_same_arms)] + match (&left.kind, &right.kind) { + // do not suggest to dereference literals + (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {}, + // &foo == &bar + (&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => { + let lty = cx.tables.expr_ty(l); + let rty = cx.tables.expr_ty(r); + let lcpy = is_copy(cx, lty); + let rcpy = is_copy(cx, rty); + // either operator autorefs or both args are copyable + if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) { + span_lint_and_then( + cx, + OP_REF, + 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(); + multispan_sugg( + diag, + "use the values directly".to_string(), + vec![(left.span, lsnip), (right.span, rsnip)], + ); + }, + ) + } else if lcpy + && !rcpy + && implements_trait(cx, lty, trait_id, &[cx.tables.expr_ty(right).into()]) + { + span_lint_and_then( + cx, + OP_REF, + 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::MaybeIncorrect, // FIXME #2597 + ); + }, + ) + } else if !lcpy + && rcpy + && implements_trait(cx, cx.tables.expr_ty(left), trait_id, &[rty.into()]) + { + span_lint_and_then( + cx, + OP_REF, + e.span, + "needlessly taken reference of right operand", + |diag| { + let rsnip = snippet(cx, r.span, "...").to_string(); + diag.span_suggestion( + right.span, + "use the right value directly", + rsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }, + ) + } + }, + // &foo == bar + (&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), _) => { + let lty = cx.tables.expr_ty(l); + let lcpy = is_copy(cx, lty); + if (requires_ref || lcpy) + && implements_trait(cx, lty, trait_id, &[cx.tables.expr_ty(right).into()]) + { + span_lint_and_then( + cx, + OP_REF, + 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::MaybeIncorrect, // FIXME #2597 + ); + }, + ) + } + }, + // foo == &bar + (_, &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => { + let rty = cx.tables.expr_ty(r); + let rcpy = is_copy(cx, rty); + if (requires_ref || rcpy) + && implements_trait(cx, cx.tables.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(); + diag.span_suggestion( + right.span, + "use the right value directly", + rsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }) + } + }, + _ => {}, + } + } + } + } +} + +fn is_valid_operator(op: BinOp) -> bool { + match op.node { + BinOpKind::Sub + | BinOpKind::Div + | BinOpKind::Eq + | BinOpKind::Lt + | BinOpKind::Le + | BinOpKind::Gt + | BinOpKind::Ge + | BinOpKind::Ne + | BinOpKind::And + | BinOpKind::Or + | BinOpKind::BitXor + | BinOpKind::BitAnd + | BinOpKind::BitOr => true, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/erasing_op.rs b/src/tools/clippy/clippy_lints/src/erasing_op.rs new file mode 100644 index 0000000000000..3ff0506e28d00 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/erasing_op.rs @@ -0,0 +1,59 @@ +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +use crate::consts::{constant_simple, Constant}; +use crate::utils::span_lint; + +declare_clippy_lint! { + /// **What it does:** Checks for erasing operations, e.g., `x * 0`. + /// + /// **Why is this bad?** The whole expression can be replaced by zero. + /// This is most likely not the intended outcome and should probably be + /// corrected + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1; + /// 0 / x; + /// 0 * x; + /// x & 0; + /// ``` + pub ERASING_OP, + correctness, + "using erasing operations, e.g., `x * 0` or `y & 0`" +} + +declare_lint_pass!(ErasingOp => [ERASING_OP]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ErasingOp { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + if let ExprKind::Binary(ref cmp, ref left, ref right) = e.kind { + match cmp.node { + BinOpKind::Mul | BinOpKind::BitAnd => { + check(cx, left, e.span); + check(cx, right, e.span); + }, + BinOpKind::Div => check(cx, left, e.span), + _ => (), + } + } + } +} + +fn check(cx: &LateContext<'_, '_>, e: &Expr<'_>, span: Span) { + if let Some(Constant::Int(0)) = constant_simple(cx, cx.tables, e) { + span_lint( + cx, + ERASING_OP, + span, + "this operation will always return zero. This is likely not the intended outcome", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs new file mode 100644 index 0000000000000..1ec60a0e6e67a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -0,0 +1,164 @@ +use rustc_hir::intravisit; +use rustc_hir::{self, Body, FnDecl, HirId, HirIdSet, ItemKind, Node}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_target::abi::LayoutOf; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase}; + +use crate::utils::span_lint; + +#[derive(Copy, Clone)] +pub struct BoxedLocal { + pub too_large_for_stack: u64, +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `Box` where an unboxed `T` would + /// work fine. + /// + /// **Why is this bad?** This is an unnecessary allocation, and bad for + /// performance. It is only necessary to allocate if you wish to move the box + /// into something. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo(bar: usize) {} + /// let x = Box::new(1); + /// foo(*x); + /// println!("{}", *x); + /// ``` + pub BOXED_LOCAL, + perf, + "using `Box` where unnecessary" +} + +fn is_non_trait_box(ty: Ty<'_>) -> bool { + ty.is_box() && !ty.boxed_ty().is_trait() +} + +struct EscapeDelegate<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + set: HirIdSet, + too_large_for_stack: u64, +} + +impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BoxedLocal { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + _: intravisit::FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + hir_id: HirId, + ) { + // If the method is an impl for a trait, don't warn. + let parent_id = cx.tcx.hir().get_parent_item(hir_id); + let parent_node = cx.tcx.hir().find(parent_id); + + if let Some(Node::Item(item)) = parent_node { + if let ItemKind::Impl { of_trait: Some(_), .. } = item.kind { + return; + } + } + + let mut v = EscapeDelegate { + cx, + set: HirIdSet::default(), + too_large_for_stack: self.too_large_for_stack, + }; + + let fn_def_id = cx.tcx.hir().local_def_id(hir_id); + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.tables).consume_body(body); + }); + + for node in v.set { + span_lint( + cx, + BOXED_LOCAL, + cx.tcx.hir().span(node), + "local variable doesn't need to be boxed here", + ); + } + } +} + +// TODO: Replace with Map::is_argument(..) when it's fixed +fn is_argument(map: rustc_middle::hir::map::Map<'_>, id: HirId) -> bool { + match map.find(id) { + Some(Node::Binding(_)) => (), + _ => return false, + } + + match map.find(map.get_parent_node(id)) { + Some(Node::Param(_)) => true, + _ => false, + } +} + +impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { + fn consume(&mut self, cmt: &Place<'tcx>, mode: ConsumeMode) { + if cmt.projections.is_empty() { + if let PlaceBase::Local(lid) = cmt.base { + if let ConsumeMode::Move = mode { + // moved out or in. clearly can't be localized + self.set.remove(&lid); + } + let map = &self.cx.tcx.hir(); + if let Some(Node::Binding(_)) = map.find(cmt.hir_id) { + if self.set.contains(&lid) { + // let y = x where x is known + // remove x, insert y + self.set.insert(cmt.hir_id); + self.set.remove(&lid); + } + } + } + } + } + + fn borrow(&mut self, cmt: &Place<'tcx>, _: ty::BorrowKind) { + if cmt.projections.is_empty() { + if let PlaceBase::Local(lid) = cmt.base { + self.set.remove(&lid); + } + } + } + + fn mutate(&mut self, cmt: &Place<'tcx>) { + if cmt.projections.is_empty() { + let map = &self.cx.tcx.hir(); + if is_argument(*map, cmt.hir_id) { + // Skip closure arguments + let parent_id = map.get_parent_node(cmt.hir_id); + if let Some(Node::Expr(..)) = map.find(map.get_parent_node(parent_id)) { + return; + } + + if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) { + self.set.insert(cmt.hir_id); + } + return; + } + } + } +} + +impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> { + fn is_large_box(&self, ty: Ty<'tcx>) -> bool { + // Large types need to be boxed to avoid stack overflows. + if ty.is_box() { + self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack + } else { + false + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs new file mode 100644 index 0000000000000..e3e1136b67693 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -0,0 +1,227 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def_id, Expr, ExprKind, Param, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{ + implements_trait, is_adjusted, iter_input_pats, snippet_opt, span_lint_and_sugg, span_lint_and_then, + type_is_unsafe_function, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for closures which just call another function where + /// the function can be called directly. `unsafe` functions or calls where types + /// get adjusted are ignored. + /// + /// **Why is this bad?** Needlessly creating a closure adds code for no benefit + /// and gives the optimizer more work. + /// + /// **Known problems:** If creating the closure inside the closure has a side- + /// effect then moving the closure creation out will change when that side- + /// effect runs. + /// See rust-lang/rust-clippy#1439 for more details. + /// + /// **Example:** + /// ```rust,ignore + /// xs.map(|x| foo(x)) + /// ``` + /// where `foo(_)` is a plain function that takes the exact argument type of + /// `x`. + pub REDUNDANT_CLOSURE, + style, + "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)" +} + +declare_clippy_lint! { + /// **What it does:** Checks for closures which only invoke a method on the closure + /// argument and can be replaced by referencing the method directly. + /// + /// **Why is this bad?** It's unnecessary to create the closure. + /// + /// **Known problems:** rust-lang/rust-clippy#3071, rust-lang/rust-clippy#4002, + /// rust-lang/rust-clippy#3942 + /// + /// + /// **Example:** + /// ```rust,ignore + /// Some('a').map(|s| s.to_uppercase()); + /// ``` + /// may be rewritten as + /// ```rust,ignore + /// Some('a').map(char::to_uppercase); + /// ``` + pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + pedantic, + "redundant closures for method calls" +} + +declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for EtaReduction { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + match expr.kind { + ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => { + for arg in args { + check_closure(cx, arg) + } + }, + _ => (), + } + } +} + +fn check_closure(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if let ExprKind::Closure(_, ref decl, eid, _, _) = expr.kind { + let body = cx.tcx.hir().body(eid); + let ex = &body.value; + + if_chain!( + if let ExprKind::Call(ref caller, ref args) = ex.kind; + + if let ExprKind::Path(_) = caller.kind; + + // Not the same number of arguments, there is no way the closure is the same as the function return; + if args.len() == decl.inputs.len(); + + // Are the expression or the arguments type-adjusted? Then we need the closure + if !(is_adjusted(cx, ex) || args.iter().any(|arg| is_adjusted(cx, arg))); + + let fn_ty = cx.tables.expr_ty(caller); + + if matches!(fn_ty.kind, ty::FnDef(_, _) | ty::FnPtr(_) | ty::Closure(_, _)); + + if !type_is_unsafe_function(cx, fn_ty); + + if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter()); + + then { + span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure found", |diag| { + if let Some(snippet) = snippet_opt(cx, caller.span) { + diag.span_suggestion( + expr.span, + "remove closure as shown", + snippet, + Applicability::MachineApplicable, + ); + } + }); + } + ); + + if_chain!( + if let ExprKind::MethodCall(ref path, _, ref args) = ex.kind; + + // Not the same number of arguments, there is no way the closure is the same as the function return; + if args.len() == decl.inputs.len(); + + // Are the expression or the arguments type-adjusted? Then we need the closure + if !(is_adjusted(cx, ex) || args.iter().skip(1).any(|arg| is_adjusted(cx, arg))); + + let method_def_id = cx.tables.type_dependent_def_id(ex.hir_id).unwrap(); + if !type_is_unsafe_function(cx, cx.tcx.type_of(method_def_id)); + + if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter()); + + if let Some(name) = get_ufcs_type_name(cx, method_def_id, &args[0]); + + then { + span_lint_and_sugg( + cx, + REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + expr.span, + "redundant closure found", + "remove closure as shown", + format!("{}::{}", name, path.ident.name), + Applicability::MachineApplicable, + ); + } + ); + } +} + +/// Tries to determine the type for universal function call to be used instead of the closure +fn get_ufcs_type_name(cx: &LateContext<'_, '_>, method_def_id: def_id::DefId, self_arg: &Expr<'_>) -> Option { + let expected_type_of_self = &cx.tcx.fn_sig(method_def_id).inputs_and_output().skip_binder()[0]; + let actual_type_of_self = &cx.tables.node_type(self_arg.hir_id); + + if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) { + if match_borrow_depth(expected_type_of_self, &actual_type_of_self) + && implements_trait(cx, actual_type_of_self, trait_id, &[]) + { + return Some(cx.tcx.def_path_str(trait_id)); + } + } + + cx.tcx.impl_of_method(method_def_id).and_then(|_| { + //a type may implicitly implement other type's methods (e.g. Deref) + if match_types(expected_type_of_self, &actual_type_of_self) { + return Some(get_type_name(cx, &actual_type_of_self)); + } + None + }) +} + +fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool { + match (&lhs.kind, &rhs.kind) { + (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(&t1, &t2), + (l, r) => match (l, r) { + (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _)) => false, + (_, _) => true, + }, + } +} + +fn match_types(lhs: Ty<'_>, rhs: Ty<'_>) -> bool { + match (&lhs.kind, &rhs.kind) { + (ty::Bool, ty::Bool) + | (ty::Char, ty::Char) + | (ty::Int(_), ty::Int(_)) + | (ty::Uint(_), ty::Uint(_)) + | (ty::Str, ty::Str) => true, + (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_types(t1, t2), + (ty::Array(t1, _), ty::Array(t2, _)) | (ty::Slice(t1), ty::Slice(t2)) => match_types(t1, t2), + (ty::Adt(def1, _), ty::Adt(def2, _)) => def1 == def2, + (_, _) => false, + } +} + +fn get_type_name(cx: &LateContext<'_, '_>, ty: Ty<'_>) -> String { + match ty.kind { + ty::Adt(t, _) => cx.tcx.def_path_str(t.did), + ty::Ref(_, r, _) => get_type_name(cx, &r), + _ => ty.to_string(), + } +} + +fn compare_inputs( + closure_inputs: &mut dyn Iterator>, + call_args: &mut dyn Iterator>, +) -> bool { + for (closure_input, function_arg) in closure_inputs.zip(call_args) { + if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind { + // XXXManishearth Should I be checking the binding mode here? + if let ExprKind::Path(QPath::Resolved(None, ref p)) = function_arg.kind { + if p.segments.len() != 1 { + // If it's a proper path, it can't be a local variable + return false; + } + if p.segments[0].ident.name != ident.name { + // The two idents should be the same + return false; + } + } else { + return false; + } + } else { + return false; + } + } + true +} diff --git a/src/tools/clippy/clippy_lints/src/eval_order_dependence.rs b/src/tools/clippy/clippy_lints/src/eval_order_dependence.rs new file mode 100644 index 0000000000000..5206266ccf2a6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/eval_order_dependence.rs @@ -0,0 +1,355 @@ +use crate::utils::{get_parent_expr, span_lint, span_lint_and_note}; +use if_chain::if_chain; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{def, BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, QPath, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for a read and a write to the same variable where + /// whether the read occurs before or after the write depends on the evaluation + /// order of sub-expressions. + /// + /// **Why is this bad?** It is often confusing to read. In addition, the + /// sub-expression evaluation order for Rust is not well documented. + /// + /// **Known problems:** Code which intentionally depends on the evaluation + /// order, or which is correct for any evaluation order. + /// + /// **Example:** + /// ```rust + /// let mut x = 0; + /// let a = { + /// x = 1; + /// 1 + /// } + x; + /// // Unclear whether a is 1 or 2. + /// ``` + pub EVAL_ORDER_DEPENDENCE, + complexity, + "whether a variable read occurs before a write depends on sub-expression evaluation order" +} + +declare_clippy_lint! { + /// **What it does:** Checks for diverging calls that are not match arms or + /// statements. + /// + /// **Why is this bad?** It is often confusing to read. In addition, the + /// sub-expression evaluation order for Rust is not well documented. + /// + /// **Known problems:** Someone might want to use `some_bool || panic!()` as a + /// shorthand. + /// + /// **Example:** + /// ```rust,no_run + /// # fn b() -> bool { true } + /// # fn c() -> bool { true } + /// let a = b() || panic!() || c(); + /// // `c()` is dead, `panic!()` is only called if `b()` returns `false` + /// let x = (a, b, c, panic!()); + /// // can simply be replaced by `panic!()` + /// ``` + pub DIVERGING_SUB_EXPRESSION, + complexity, + "whether an expression contains a diverging sub expression" +} + +declare_lint_pass!(EvalOrderDependence => [EVAL_ORDER_DEPENDENCE, DIVERGING_SUB_EXPRESSION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for EvalOrderDependence { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + // Find a write to a local variable. + match expr.kind { + ExprKind::Assign(ref lhs, ..) | ExprKind::AssignOp(_, ref lhs, _) => { + if let ExprKind::Path(ref qpath) = lhs.kind { + if let QPath::Resolved(_, ref path) = *qpath { + if path.segments.len() == 1 { + if let def::Res::Local(var) = cx.tables.qpath_res(qpath, lhs.hir_id) { + let mut visitor = ReadVisitor { + cx, + var, + write_expr: expr, + last_expr: expr, + }; + check_for_unsequenced_reads(&mut visitor); + } + } + } + } + }, + _ => {}, + } + } + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) { + match stmt.kind { + StmtKind::Local(ref local) => { + if let Local { init: Some(ref e), .. } = **local { + DivergenceVisitor { cx }.visit_expr(e); + } + }, + StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => DivergenceVisitor { cx }.maybe_walk_expr(e), + StmtKind::Item(..) => {}, + } + } +} + +struct DivergenceVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> { + fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) { + match e.kind { + ExprKind::Closure(..) => {}, + ExprKind::Match(ref e, arms, _) => { + self.visit_expr(e); + for arm in arms { + if let Some(Guard::If(if_expr)) = arm.guard { + self.visit_expr(if_expr) + } + // make sure top level arm expressions aren't linted + self.maybe_walk_expr(&*arm.body); + } + }, + _ => walk_expr(self, e), + } + } + fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) { + span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges"); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + match e.kind { + ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e), + ExprKind::Call(ref func, _) => { + let typ = self.cx.tables.expr_ty(func); + match typ.kind { + ty::FnDef(..) | ty::FnPtr(_) => { + let sig = typ.fn_sig(self.cx.tcx); + if let ty::Never = self.cx.tcx.erase_late_bound_regions(&sig).output().kind { + self.report_diverging_sub_expr(e); + } + }, + _ => {}, + } + }, + ExprKind::MethodCall(..) => { + let borrowed_table = self.cx.tables; + if borrowed_table.expr_ty(e).is_never() { + self.report_diverging_sub_expr(e); + } + }, + _ => { + // do not lint expressions referencing objects of type `!`, as that required a + // diverging expression + // to begin with + }, + } + self.maybe_walk_expr(e); + } + fn visit_block(&mut self, _: &'tcx Block<'_>) { + // don't continue over blocks, LateLintPass already does that + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Walks up the AST from the given write expression (`vis.write_expr`) looking +/// for reads to the same variable that are unsequenced relative to the write. +/// +/// This means reads for which there is a common ancestor between the read and +/// the write such that +/// +/// * evaluating the ancestor necessarily evaluates both the read and the write (for example, `&x` +/// and `|| x = 1` don't necessarily evaluate `x`), and +/// +/// * which one is evaluated first depends on the order of sub-expression evaluation. Blocks, `if`s, +/// loops, `match`es, and the short-circuiting logical operators are considered to have a defined +/// evaluation order. +/// +/// When such a read is found, the lint is triggered. +fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) { + let map = &vis.cx.tcx.hir(); + let mut cur_id = vis.write_expr.hir_id; + loop { + let parent_id = map.get_parent_node(cur_id); + if parent_id == cur_id { + break; + } + let parent_node = match map.find(parent_id) { + Some(parent) => parent, + None => break, + }; + + let stop_early = match parent_node { + Node::Expr(expr) => check_expr(vis, expr), + Node::Stmt(stmt) => check_stmt(vis, stmt), + Node::Item(_) => { + // We reached the top of the function, stop. + break; + }, + _ => StopEarly::KeepGoing, + }; + match stop_early { + StopEarly::Stop => break, + StopEarly::KeepGoing => {}, + } + + cur_id = parent_id; + } +} + +/// Whether to stop early for the loop in `check_for_unsequenced_reads`. (If +/// `check_expr` weren't an independent function, this would be unnecessary and +/// we could just use `break`). +enum StopEarly { + KeepGoing, + Stop, +} + +fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly { + if expr.hir_id == vis.last_expr.hir_id { + return StopEarly::KeepGoing; + } + + match expr.kind { + ExprKind::Array(_) + | ExprKind::Tup(_) + | ExprKind::MethodCall(..) + | ExprKind::Call(_, _) + | ExprKind::Assign(..) + | ExprKind::Index(_, _) + | ExprKind::Repeat(_, _) + | ExprKind::Struct(_, _, _) => { + walk_expr(vis, expr); + }, + ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => { + if op.node == BinOpKind::And || op.node == BinOpKind::Or { + // x && y and x || y always evaluate x first, so these are + // strictly sequenced. + } else { + walk_expr(vis, expr); + } + }, + ExprKind::Closure(_, _, _, _, _) => { + // Either + // + // * `var` is defined in the closure body, in which case we've reached the top of the enclosing + // function and can stop, or + // + // * `var` is captured by the closure, in which case, because evaluating a closure does not evaluate + // its body, we don't necessarily have a write, so we need to stop to avoid generating false + // positives. + // + // This is also the only place we need to stop early (grrr). + return StopEarly::Stop; + }, + // All other expressions either have only one child or strictly + // sequence the evaluation order of their sub-expressions. + _ => {}, + } + + vis.last_expr = expr; + + StopEarly::KeepGoing +} + +fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => check_expr(vis, expr), + // If the declaration is of a local variable, check its initializer + // expression if it has one. Otherwise, keep going. + StmtKind::Local(ref local) => local + .init + .as_ref() + .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)), + _ => StopEarly::KeepGoing, + } +} + +/// A visitor that looks for reads from a variable. +struct ReadVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + /// The ID of the variable we're looking for. + var: HirId, + /// The expressions where the write to the variable occurred (for reporting + /// in the lint). + write_expr: &'tcx Expr<'tcx>, + /// The last (highest in the AST) expression we've checked, so we know not + /// to recheck it. + last_expr: &'tcx Expr<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if expr.hir_id == self.last_expr.hir_id { + return; + } + + match expr.kind { + ExprKind::Path(ref qpath) => { + if_chain! { + if let QPath::Resolved(None, ref path) = *qpath; + if path.segments.len() == 1; + if let def::Res::Local(local_id) = self.cx.tables.qpath_res(qpath, expr.hir_id); + if local_id == self.var; + // Check that this is a read, not a write. + if !is_in_assignment_position(self.cx, expr); + then { + span_lint_and_note( + self.cx, + EVAL_ORDER_DEPENDENCE, + expr.span, + "unsequenced read of a variable", + Some(self.write_expr.span), + "whether read occurs before this write depends on evaluation order" + ); + } + } + } + // We're about to descend a closure. Since we don't know when (or + // if) the closure will be evaluated, any reads in it might not + // occur here (or ever). Like above, bail to avoid false positives. + ExprKind::Closure(_, _, _, _, _) | + + // We want to avoid a false positive when a variable name occurs + // only to have its address taken, so we stop here. Technically, + // this misses some weird cases, eg. + // + // ```rust + // let mut x = 0; + // let a = foo(&{x = 1; x}, x); + // ``` + // + // TODO: fix this + ExprKind::AddrOf(_, _, _) => { + return; + } + _ => {} + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`. +fn is_in_assignment_position(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, expr) { + if let ExprKind::Assign(ref lhs, ..) = parent.kind { + return lhs.hir_id == expr.hir_id; + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/excessive_bools.rs b/src/tools/clippy/clippy_lints/src/excessive_bools.rs new file mode 100644 index 0000000000000..82ca4baacb7a9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/excessive_bools.rs @@ -0,0 +1,176 @@ +use crate::utils::{attr_by_name, in_macro, match_path_ast, span_lint_and_help}; +use rustc_ast::ast::{AssocItemKind, Extern, FnSig, Item, ItemKind, Ty, TyKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; + +use std::convert::TryInto; + +declare_clippy_lint! { + /// **What it does:** Checks for excessive + /// use of bools in structs. + /// + /// **Why is this bad?** Excessive bools in a struct + /// is often a sign that it's used as a state machine, + /// which is much better implemented as an enum. + /// If it's not the case, excessive bools usually benefit + /// from refactoring into two-variant enums for better + /// readability and API. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// struct S { + /// is_pending: bool, + /// is_processing: bool, + /// is_finished: bool, + /// } + /// ``` + /// + /// Good: + /// ```rust + /// enum S { + /// Pending, + /// Processing, + /// Finished, + /// } + /// ``` + pub STRUCT_EXCESSIVE_BOOLS, + pedantic, + "using too many bools in a struct" +} + +declare_clippy_lint! { + /// **What it does:** Checks for excessive use of + /// bools in function definitions. + /// + /// **Why is this bad?** Calls to such functions + /// are confusing and error prone, because it's + /// hard to remember argument order and you have + /// no type system support to back you up. Using + /// two-variant enums instead of bools often makes + /// API easier to use. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// fn f(is_round: bool, is_hot: bool) { ... } + /// ``` + /// + /// Good: + /// ```rust,ignore + /// enum Shape { + /// Round, + /// Spiky, + /// } + /// + /// enum Temperature { + /// Hot, + /// IceCold, + /// } + /// + /// fn f(shape: Shape, temperature: Temperature) { ... } + /// ``` + pub FN_PARAMS_EXCESSIVE_BOOLS, + pedantic, + "using too many bools in function parameters" +} + +pub struct ExcessiveBools { + max_struct_bools: u64, + max_fn_params_bools: u64, +} + +impl ExcessiveBools { + #[must_use] + pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self { + Self { + max_struct_bools, + max_fn_params_bools, + } + } + + fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) { + match fn_sig.header.ext { + Extern::Implicit | Extern::Explicit(_) => return, + Extern::None => (), + } + + let fn_sig_bools = fn_sig + .decl + .inputs + .iter() + .filter(|param| is_bool_ty(¶m.ty)) + .count() + .try_into() + .unwrap(); + if self.max_fn_params_bools < fn_sig_bools { + span_lint_and_help( + cx, + FN_PARAMS_EXCESSIVE_BOOLS, + span, + &format!("more than {} bools in function parameters", self.max_fn_params_bools), + None, + "consider refactoring bools into two-variant enums", + ); + } + } +} + +impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]); + +fn is_bool_ty(ty: &Ty) -> bool { + if let TyKind::Path(None, path) = &ty.kind { + return match_path_ast(path, &["bool"]); + } + false +} + +impl EarlyLintPass for ExcessiveBools { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if in_macro(item.span) { + return; + } + match &item.kind { + ItemKind::Struct(variant_data, _) => { + if attr_by_name(&item.attrs, "repr").is_some() { + return; + } + + let struct_bools = variant_data + .fields() + .iter() + .filter(|field| is_bool_ty(&field.ty)) + .count() + .try_into() + .unwrap(); + if self.max_struct_bools < struct_bools { + span_lint_and_help( + cx, + STRUCT_EXCESSIVE_BOOLS, + item.span, + &format!("more than {} bools in a struct", self.max_struct_bools), + None, + "consider using a state machine or refactoring bools into two-variant enums", + ); + } + }, + ItemKind::Impl { + of_trait: None, items, .. + } + | ItemKind::Trait(_, _, _, _, items) => { + for item in items { + if let AssocItemKind::Fn(_, fn_sig, _, _) = &item.kind { + self.check_fn_sig(cx, fn_sig, item.span); + } + } + }, + ItemKind::Fn(_, fn_sig, _, _) => self.check_fn_sig(cx, fn_sig, item.span), + _ => (), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/exit.rs b/src/tools/clippy/clippy_lints/src/exit.rs new file mode 100644 index 0000000000000..621d56185a9dd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/exit.rs @@ -0,0 +1,47 @@ +use crate::utils::{is_entrypoint_fn, match_def_path, paths, qpath_res, span_lint}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** `exit()` terminates the program and doesn't provide a + /// stack trace. + /// + /// **Why is this bad?** Ideally a program is terminated by finishing + /// the main function. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// std::process::exit(0) + /// ``` + pub EXIT, + restriction, + "`std::process::exit` is called, terminating the program" +} + +declare_lint_pass!(Exit => [EXIT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Exit { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref path_expr, ref _args) = e.kind; + if let ExprKind::Path(ref path) = path_expr.kind; + if let Some(def_id) = qpath_res(cx, path, path_expr.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::EXIT); + then { + let parent = cx.tcx.hir().get_parent_item(e.hir_id); + if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent) { + // If the next item up is a function we check if it is an entry point + // and only then emit a linter warning + let def_id = cx.tcx.hir().local_def_id(parent); + if !is_entrypoint_fn(cx, def_id.to_def_id()) { + span_lint(cx, EXIT, e.span, "usage of `process::exit`"); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/explicit_write.rs b/src/tools/clippy/clippy_lints/src/explicit_write.rs new file mode 100644 index 0000000000000..320121b277140 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/explicit_write.rs @@ -0,0 +1,149 @@ +use crate::utils::{is_expn_of, match_function_call, paths, span_lint, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `write!()` / `writeln()!` which can be + /// replaced with `(e)print!()` / `(e)println!()` + /// + /// **Why is this bad?** Using `(e)println! is clearer and more concise + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::io::Write; + /// # let bar = "furchtbar"; + /// // this would be clearer as `eprintln!("foo: {:?}", bar);` + /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap(); + /// ``` + pub EXPLICIT_WRITE, + complexity, + "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work" +} + +declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitWrite { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + // match call to unwrap + if let ExprKind::MethodCall(ref unwrap_fun, _, ref unwrap_args) = expr.kind; + if unwrap_fun.ident.name == sym!(unwrap); + // match call to write_fmt + if !unwrap_args.is_empty(); + if let ExprKind::MethodCall(ref write_fun, _, write_args) = + unwrap_args[0].kind; + if write_fun.ident.name == sym!(write_fmt); + // match calls to std::io::stdout() / std::io::stderr () + if !write_args.is_empty(); + if let Some(dest_name) = if match_function_call(cx, &write_args[0], &paths::STDOUT).is_some() { + Some("stdout") + } else if match_function_call(cx, &write_args[0], &paths::STDERR).is_some() { + Some("stderr") + } else { + None + }; + then { + let write_span = unwrap_args[0].span; + let calling_macro = + // ordering is important here, since `writeln!` uses `write!` internally + if is_expn_of(write_span, "writeln").is_some() { + Some("writeln") + } else if is_expn_of(write_span, "write").is_some() { + Some("write") + } else { + None + }; + let prefix = if dest_name == "stderr" { + "e" + } else { + "" + }; + + // We need to remove the last trailing newline from the string because the + // underlying `fmt::write` function doesn't know whether `println!` or `print!` was + // used. + if let Some(mut write_output) = write_output_string(write_args) { + if write_output.ends_with('\n') { + write_output.pop(); + } + + if let Some(macro_name) = calling_macro { + span_lint_and_sugg( + cx, + EXPLICIT_WRITE, + expr.span, + &format!( + "use of `{}!({}(), ...).unwrap()`", + macro_name, + dest_name + ), + "try this", + format!("{}{}!(\"{}\")", prefix, macro_name.replace("write", "print"), write_output.escape_default()), + Applicability::MachineApplicable + ); + } else { + span_lint_and_sugg( + cx, + EXPLICIT_WRITE, + expr.span, + &format!("use of `{}().write_fmt(...).unwrap()`", dest_name), + "try this", + format!("{}print!(\"{}\")", prefix, write_output.escape_default()), + Applicability::MachineApplicable + ); + } + } else { + // We don't have a proper suggestion + if let Some(macro_name) = calling_macro { + span_lint( + cx, + EXPLICIT_WRITE, + expr.span, + &format!( + "use of `{}!({}(), ...).unwrap()`. Consider using `{}{}!` instead", + macro_name, + dest_name, + prefix, + macro_name.replace("write", "print") + ) + ); + } else { + span_lint( + cx, + EXPLICIT_WRITE, + expr.span, + &format!("use of `{}().write_fmt(...).unwrap()`. Consider using `{}print!` instead", dest_name, prefix), + ); + } + } + + } + } + } +} + +// Extract the output string from the given `write_args`. +fn write_output_string(write_args: &[Expr<'_>]) -> Option { + if_chain! { + // Obtain the string that should be printed + if write_args.len() > 1; + if let ExprKind::Call(_, ref output_args) = write_args[1].kind; + if !output_args.is_empty(); + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref output_string_expr) = output_args[0].kind; + if let ExprKind::Array(ref string_exprs) = output_string_expr.kind; + // we only want to provide an automatic suggestion for simple (non-format) strings + if string_exprs.len() == 1; + if let ExprKind::Lit(ref lit) = string_exprs[0].kind; + if let LitKind::Str(ref write_output, _) = lit.node; + then { + return Some(write_output.to_string()) + } + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs b/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs new file mode 100644 index 0000000000000..17639cc2a0643 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/fallible_impl_from.rs @@ -0,0 +1,130 @@ +use crate::utils::paths::{BEGIN_PANIC, BEGIN_PANIC_FMT, FROM_TRAIT}; +use crate::utils::{ + is_expn_of, is_type_diagnostic_item, match_def_path, method_chain_args, span_lint_and_then, walk_ptrs_ty, +}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for impls of `From<..>` that contain `panic!()` or `unwrap()` + /// + /// **Why is this bad?** `TryFrom` should be used if there's a possibility of failure. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct Foo(i32); + /// impl From for Foo { + /// fn from(s: String) -> Self { + /// Foo(s.parse().unwrap()) + /// } + /// } + /// ``` + pub FALLIBLE_IMPL_FROM, + nursery, + "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`" +} + +declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FallibleImplFrom { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + // check for `impl From for ..` + let impl_def_id = cx.tcx.hir().local_def_id(item.hir_id); + if_chain! { + if let hir::ItemKind::Impl{ items: impl_items, .. } = item.kind; + if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_def_id); + if match_def_path(cx, impl_trait_ref.def_id, &FROM_TRAIT); + then { + lint_impl_body(cx, item.span, impl_items); + } + } + } +} + +fn lint_impl_body<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef<'_>]) { + use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; + use rustc_hir::{Expr, ExprKind, ImplItemKind, QPath}; + + struct FindPanicUnwrap<'a, 'tcx> { + lcx: &'a LateContext<'a, 'tcx>, + tables: &'tcx ty::TypeckTables<'tcx>, + result: Vec, + } + + impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // check for `begin_panic` + if_chain! { + if let ExprKind::Call(ref func_expr, _) = expr.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; + if let Some(path_def_id) = path.res.opt_def_id(); + if match_def_path(self.lcx, path_def_id, &BEGIN_PANIC) || + match_def_path(self.lcx, path_def_id, &BEGIN_PANIC_FMT); + if is_expn_of(expr.span, "unreachable").is_none(); + then { + self.result.push(expr.span); + } + } + + // check for `unwrap` + if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { + let reciever_ty = walk_ptrs_ty(self.tables.expr_ty(&arglists[0][0])); + if is_type_diagnostic_item(self.lcx, reciever_ty, sym!(option_type)) + || is_type_diagnostic_item(self.lcx, reciever_ty, sym!(result_type)) + { + self.result.push(expr.span); + } + } + + // and check sub-expressions + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + } + + for impl_item in impl_items { + if_chain! { + if impl_item.ident.name == sym!(from); + if let ImplItemKind::Fn(_, body_id) = + cx.tcx.hir().impl_item(impl_item.id).kind; + then { + // check the body for `begin_panic` or `unwrap` + let body = cx.tcx.hir().body(body_id); + let impl_item_def_id = cx.tcx.hir().local_def_id(impl_item.id.hir_id); + let mut fpu = FindPanicUnwrap { + lcx: cx, + tables: cx.tcx.typeck_tables_of(impl_item_def_id), + result: Vec::new(), + }; + fpu.visit_expr(&body.value); + + // if we've found one, lint + if !fpu.result.is_empty() { + span_lint_and_then( + cx, + FALLIBLE_IMPL_FROM, + impl_span, + "consider implementing `TryFrom` instead", + move |diag| { + diag.help( + "`From` is intended for infallible conversions only. \ + Use `TryFrom` if there's a possibility for the conversion to fail."); + diag.span_note(fpu.result, "potential failure(s)"); + }); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/float_literal.rs b/src/tools/clippy/clippy_lints/src/float_literal.rs new file mode 100644 index 0000000000000..3a52b1d3fc20b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/float_literal.rs @@ -0,0 +1,182 @@ +use crate::utils::{numeric_literal, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::fmt; + +declare_clippy_lint! { + /// **What it does:** Checks for float literals with a precision greater + /// than that supported by the underlying type. + /// + /// **Why is this bad?** Rust will truncate the literal silently. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let v: f32 = 0.123_456_789_9; + /// println!("{}", v); // 0.123_456_789 + /// + /// // Good + /// let v: f64 = 0.123_456_789_9; + /// println!("{}", v); // 0.123_456_789_9 + /// ``` + pub EXCESSIVE_PRECISION, + style, + "excessive precision for float literal" +} + +declare_clippy_lint! { + /// **What it does:** Checks for whole number float literals that + /// cannot be represented as the underlying type without loss. + /// + /// **Why is this bad?** Rust will silently lose precision during + /// conversion to a float. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let _: f32 = 16_777_217.0; // 16_777_216.0 + /// + /// // Good + /// let _: f32 = 16_777_216.0; + /// let _: f64 = 16_777_217.0; + /// ``` + pub LOSSY_FLOAT_LITERAL, + restriction, + "lossy whole number float literals" +} + +declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatLiteral { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + let ty = cx.tables.expr_ty(expr); + if let ty::Float(fty) = ty.kind; + if let hir::ExprKind::Lit(ref lit) = expr.kind; + if let LitKind::Float(sym, lit_float_ty) = lit.node; + then { + let sym_str = sym.as_str(); + let formatter = FloatFormat::new(&sym_str); + // Try to bail out if the float is for sure fine. + // If its within the 2 decimal digits of being out of precision we + // check if the parsed representation is the same as the string + // since we'll need the truncated string anyway. + let digits = count_digits(&sym_str); + let max = max_digits(fty); + let type_suffix = match lit_float_ty { + LitFloatType::Suffixed(FloatTy::F32) => Some("f32"), + LitFloatType::Suffixed(FloatTy::F64) => Some("f64"), + _ => None + }; + let (is_whole, mut float_str) = match fty { + FloatTy::F32 => { + let value = sym_str.parse::().unwrap(); + + (value.fract() == 0.0, formatter.format(value)) + }, + FloatTy::F64 => { + let value = sym_str.parse::().unwrap(); + + (value.fract() == 0.0, formatter.format(value)) + }, + }; + + if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') { + // Normalize the literal by stripping the fractional portion + if sym_str.split('.').next().unwrap() != float_str { + // If the type suffix is missing the suggestion would be + // incorrectly interpreted as an integer so adding a `.0` + // suffix to prevent that. + if type_suffix.is_none() { + float_str.push_str(".0"); + } + + span_lint_and_sugg( + cx, + LOSSY_FLOAT_LITERAL, + expr.span, + "literal cannot be represented as the underlying type without loss of precision", + "consider changing the type or replacing it with", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + ); + } + } else if digits > max as usize && sym_str != float_str { + span_lint_and_sugg( + cx, + EXCESSIVE_PRECISION, + expr.span, + "float has excessive precision", + "consider changing the type or truncating it to", + numeric_literal::format(&float_str, type_suffix, true), + Applicability::MachineApplicable, + ); + } + } + } + } +} + +#[must_use] +fn max_digits(fty: FloatTy) -> u32 { + match fty { + FloatTy::F32 => f32::DIGITS, + FloatTy::F64 => f64::DIGITS, + } +} + +/// Counts the digits excluding leading zeros +#[must_use] +fn count_digits(s: &str) -> usize { + // Note that s does not contain the f32/64 suffix, and underscores have been stripped + s.chars() + .filter(|c| *c != '-' && *c != '.') + .take_while(|c| *c != 'e' && *c != 'E') + .fold(0, |count, c| { + // leading zeros + if c == '0' && count == 0 { + count + } else { + count + 1 + } + }) +} + +enum FloatFormat { + LowerExp, + UpperExp, + Normal, +} +impl FloatFormat { + #[must_use] + fn new(s: &str) -> Self { + s.chars() + .find_map(|x| match x { + 'e' => Some(Self::LowerExp), + 'E' => Some(Self::UpperExp), + _ => None, + }) + .unwrap_or(Self::Normal) + } + fn format(&self, f: T) -> String + where + T: fmt::UpperExp + fmt::LowerExp + fmt::Display, + { + match self { + Self::LowerExp => format!("{:e}", f), + Self::UpperExp => format!("{:E}", f), + Self::Normal => format!("{}", f), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs new file mode 100644 index 0000000000000..86317fb8bd5c4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs @@ -0,0 +1,503 @@ +use crate::consts::{ + constant, constant_simple, Constant, + Constant::{F32, F64}, +}; +use crate::utils::{higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +use rustc_ast::ast; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; +use sugg::Sugg; + +declare_clippy_lint! { + /// **What it does:** Looks for floating-point expressions that + /// can be expressed using built-in methods to improve accuracy + /// at the cost of performance. + /// + /// **Why is this bad?** Negatively impacts accuracy. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// + /// let a = 3f32; + /// let _ = a.powf(1.0 / 3.0); + /// let _ = (1.0 + a).ln(); + /// let _ = a.exp() - 1.0; + /// ``` + /// + /// is better expressed as + /// + /// ```rust + /// + /// let a = 3f32; + /// let _ = a.cbrt(); + /// let _ = a.ln_1p(); + /// let _ = a.exp_m1(); + /// ``` + pub IMPRECISE_FLOPS, + nursery, + "usage of imprecise floating point operations" +} + +declare_clippy_lint! { + /// **What it does:** Looks for floating-point expressions that + /// can be expressed using built-in methods to improve both + /// accuracy and performance. + /// + /// **Why is this bad?** Negatively impacts accuracy and performance. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// use std::f32::consts::E; + /// + /// let a = 3f32; + /// let _ = (2f32).powf(a); + /// let _ = E.powf(a); + /// let _ = a.powf(1.0 / 2.0); + /// let _ = a.log(2.0); + /// let _ = a.log(10.0); + /// let _ = a.log(E); + /// let _ = a.powf(2.0); + /// let _ = a * 2.0 + 4.0; + /// let _ = if a < 0.0 { + /// -a + /// } else { + /// a + /// }; + /// let _ = if a < 0.0 { + /// a + /// } else { + /// -a + /// }; + /// ``` + /// + /// is better expressed as + /// + /// ```rust + /// use std::f32::consts::E; + /// + /// let a = 3f32; + /// let _ = a.exp2(); + /// let _ = a.exp(); + /// let _ = a.sqrt(); + /// let _ = a.log2(); + /// let _ = a.log10(); + /// let _ = a.ln(); + /// let _ = a.powi(2); + /// let _ = a.mul_add(2.0, 4.0); + /// let _ = a.abs(); + /// let _ = -a.abs(); + /// ``` + pub SUBOPTIMAL_FLOPS, + nursery, + "usage of sub-optimal floating point operations" +} + +declare_lint_pass!(FloatingPointArithmetic => [ + IMPRECISE_FLOPS, + SUBOPTIMAL_FLOPS +]); + +// Returns the specialized log method for a given base if base is constant +// and is one of 2, 10 and e +fn get_specialized_log_method(cx: &LateContext<'_, '_>, base: &Expr<'_>) -> Option<&'static str> { + if let Some((value, _)) = constant(cx, cx.tables, base) { + if F32(2.0) == value || F64(2.0) == value { + return Some("log2"); + } else if F32(10.0) == value || F64(10.0) == value { + return Some("log10"); + } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + return Some("ln"); + } + } + + None +} + +// Adds type suffixes and parenthesis to method receivers if necessary +fn prepare_receiver_sugg<'a>(cx: &LateContext<'_, '_>, mut expr: &'a Expr<'a>) -> Sugg<'a> { + let mut suggestion = Sugg::hir(cx, expr, ".."); + + if let ExprKind::Unary(UnOp::UnNeg, inner_expr) = &expr.kind { + expr = &inner_expr; + } + + if_chain! { + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + if let ty::Float(float_ty) = cx.tables.expr_ty(expr).kind; + if let ExprKind::Lit(lit) = &expr.kind; + if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node; + then { + let op = format!( + "{}{}{}", + suggestion, + // Check for float literals without numbers following the decimal + // separator such as `2.` and adds a trailing zero + if sym.as_str().ends_with('.') { + "0" + } else { + "" + }, + float_ty.name_str() + ).into(); + + suggestion = match suggestion { + Sugg::MaybeParen(_) => Sugg::MaybeParen(op), + _ => Sugg::NonParen(op) + }; + } + } + + suggestion.maybe_par() +} + +fn check_log_base(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) { + if let Some(method) = get_specialized_log_method(cx, &args[1]) { + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "logarithm for bases 2, 10 and e can be computed more accurately", + "consider using", + format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method), + Applicability::MachineApplicable, + ); + } +} + +// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and +// suggest usage of `(x + (y - 1)).ln_1p()` instead +fn check_ln1p(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + lhs, + rhs, + ) = &args[0].kind + { + let recv = match (constant(cx, cx.tables, lhs), constant(cx, cx.tables, rhs)) { + (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs, + (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs, + _ => return, + }; + + span_lint_and_sugg( + cx, + IMPRECISE_FLOPS, + expr.span, + "ln(1 + x) can be computed more accurately", + "consider using", + format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)), + Applicability::MachineApplicable, + ); + } +} + +// Returns an integer if the float constant is a whole number and it can be +// converted to an integer without loss of precision. For now we only check +// ranges [-16777215, 16777216) for type f32 as whole number floats outside +// this range are lossy and ambiguous. +#[allow(clippy::cast_possible_truncation)] +fn get_integer_from_float_constant(value: &Constant) -> Option { + match value { + F32(num) if num.fract() == 0.0 => { + if (-16_777_215.0..16_777_216.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + F64(num) if num.fract() == 0.0 => { + if (-2_147_483_648.0..2_147_483_648.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + _ => None, + } +} + +fn check_powf(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args: &[Expr<'_>]) { + // Check receiver + if let Some((value, _)) = constant(cx, cx.tables, &args[0]) { + let method = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + "exp" + } else if F32(2.0) == value || F64(2.0) == value { + "exp2" + } else { + return; + }; + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "exponent for bases 2 and e can be computed more accurately", + "consider using", + format!("{}.{}()", prepare_receiver_sugg(cx, &args[1]), method), + Applicability::MachineApplicable, + ); + } + + // Check argument + if let Some((value, _)) = constant(cx, cx.tables, &args[1]) { + let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { + ( + SUBOPTIMAL_FLOPS, + "square-root of a number can be computed more efficiently and accurately", + format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")), + ) + } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { + ( + IMPRECISE_FLOPS, + "cube-root of a number can be computed more accurately", + format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")), + ) + } else if let Some(exponent) = get_integer_from_float_constant(&value) { + ( + SUBOPTIMAL_FLOPS, + "exponentiation with integer powers can be computed more efficiently", + format!( + "{}.powi({})", + Sugg::hir(cx, &args[0], ".."), + numeric_literal::format(&exponent.to_string(), None, false) + ), + ) + } else { + return; + }; + + span_lint_and_sugg( + cx, + lint, + expr.span, + help, + "consider using", + suggestion, + Applicability::MachineApplicable, + ); + } +} + +// TODO: Lint expressions of the form `x.exp() - y` where y > 1 +// and suggest usage of `x.exp_m1() - (y - 1)` instead +fn check_expm1(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, ref lhs, ref rhs) = expr.kind; + if cx.tables.expr_ty(lhs).is_floating_point(); + if let Some((value, _)) = constant(cx, cx.tables, rhs); + if F32(1.0) == value || F64(1.0) == value; + if let ExprKind::MethodCall(ref path, _, ref method_args) = lhs.kind; + if cx.tables.expr_ty(&method_args[0]).is_floating_point(); + if path.ident.name.as_str() == "exp"; + then { + span_lint_and_sugg( + cx, + IMPRECISE_FLOPS, + expr.span, + "(e.pow(x) - 1) can be computed more accurately", + "consider using", + format!( + "{}.exp_m1()", + Sugg::hir(cx, &method_args[0], "..") + ), + Applicability::MachineApplicable, + ); + } + } +} + +fn is_float_mul_expr<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { + if_chain! { + if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lhs, ref rhs) = &expr.kind; + if cx.tables.expr_ty(lhs).is_floating_point(); + if cx.tables.expr_ty(rhs).is_floating_point(); + then { + return Some((lhs, rhs)); + } + } + + None +} + +// TODO: Fix rust-lang/rust-clippy#4735 +fn check_mul_add(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + lhs, + rhs, + ) = &expr.kind + { + let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) { + (inner_lhs, inner_rhs, rhs) + } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) { + (inner_lhs, inner_rhs, lhs) + } else { + return; + }; + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "multiply and add expressions can be calculated more efficiently and accurately", + "consider using", + format!( + "{}.mul_add({}, {})", + prepare_receiver_sugg(cx, recv), + Sugg::hir(cx, arg1, ".."), + Sugg::hir(cx, arg2, ".."), + ), + Applicability::MachineApplicable, + ); + } +} + +/// Returns true iff expr is an expression which tests whether or not +/// test is positive or an expression which tests whether or not test +/// is nonnegative. +/// Used for check-custom-abs function below +fn is_testing_positive(cx: &LateContext<'_, '_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { + if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { + match op { + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && are_exprs_equal(cx, left, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && are_exprs_equal(cx, right, test), + _ => false, + } + } else { + false + } +} + +/// See [`is_testing_positive`] +fn is_testing_negative(cx: &LateContext<'_, '_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { + if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { + match op { + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && are_exprs_equal(cx, right, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && are_exprs_equal(cx, left, test), + _ => false, + } + } else { + false + } +} + +fn are_exprs_equal(cx: &LateContext<'_, '_>, expr1: &Expr<'_>, expr2: &Expr<'_>) -> bool { + SpanlessEq::new(cx).ignore_fn().eq_expr(expr1, expr2) +} + +/// Returns true iff expr is some zero literal +fn is_zero(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + match constant_simple(cx, cx.tables, expr) { + Some(Constant::Int(i)) => i == 0, + Some(Constant::F32(f)) => f == 0.0, + Some(Constant::F64(f)) => f == 0.0, + _ => false, + } +} + +/// If the two expressions are negations of each other, then it returns +/// a tuple, in which the first element is true iff expr1 is the +/// positive expressions, and the second element is the positive +/// one of the two expressions +/// If the two expressions are not negations of each other, then it +/// returns None. +fn are_negated<'a>(cx: &LateContext<'_, '_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { + if let ExprKind::Unary(UnOp::UnNeg, expr1_negated) = &expr1.kind { + if are_exprs_equal(cx, expr1_negated, expr2) { + return Some((false, expr2)); + } + } + if let ExprKind::Unary(UnOp::UnNeg, expr2_negated) = &expr2.kind { + if are_exprs_equal(cx, expr1, expr2_negated) { + return Some((true, expr1)); + } + } + None +} + +fn check_custom_abs(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let Some((cond, body, Some(else_body))) = higher::if_block(&expr); + if let ExprKind::Block(block, _) = body.kind; + if block.stmts.is_empty(); + if let Some(if_body_expr) = block.expr; + if let ExprKind::Block(else_block, _) = else_body.kind; + if else_block.stmts.is_empty(); + if let Some(else_body_expr) = else_block.expr; + if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr); + then { + let positive_abs_sugg = ( + "manual implementation of `abs` method", + format!("{}.abs()", Sugg::hir(cx, body, "..")), + ); + let negative_abs_sugg = ( + "manual implementation of negation of `abs` method", + format!("-{}.abs()", Sugg::hir(cx, body, "..")), + ); + let sugg = if is_testing_positive(cx, cond, body) { + if if_expr_positive { + positive_abs_sugg + } else { + negative_abs_sugg + } + } else if is_testing_negative(cx, cond, body) { + if if_expr_positive { + negative_abs_sugg + } else { + positive_abs_sugg + } + } else { + return; + }; + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + sugg.0, + "try", + sugg.1, + Applicability::MachineApplicable, + ); + } + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::MethodCall(ref path, _, args) = &expr.kind { + let recv_ty = cx.tables.expr_ty(&args[0]); + + if recv_ty.is_floating_point() { + match &*path.ident.name.as_str() { + "ln" => check_ln1p(cx, expr, args), + "log" => check_log_base(cx, expr, args), + "powf" => check_powf(cx, expr, args), + _ => {}, + } + } + } else { + check_expm1(cx, expr); + check_mul_add(cx, expr); + check_custom_abs(cx, expr); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/format.rs b/src/tools/clippy/clippy_lints/src/format.rs new file mode 100644 index 0000000000000..5b092526ce4f2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/format.rs @@ -0,0 +1,200 @@ +use crate::utils::paths; +use crate::utils::{ + is_expn_of, is_type_diagnostic_item, last_path_segment, match_def_path, match_function_call, snippet, + span_lint_and_then, walk_ptrs_ty, +}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, MatchSource, PatKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `format!("string literal with no + /// argument")` and `format!("{}", foo)` where `foo` is a string. + /// + /// **Why is this bad?** There is no point of doing that. `format!("foo")` can + /// be replaced by `"foo".to_owned()` if you really need a `String`. The even + /// worse `&format!("foo")` is often encountered in the wild. `format!("{}", + /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()` + /// if `foo: &str`. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// # let foo = "foo"; + /// format!("foo"); + /// format!("{}", foo); + /// ``` + pub USELESS_FORMAT, + complexity, + "useless use of `format!`" +} + +declare_lint_pass!(UselessFormat => [USELESS_FORMAT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessFormat { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + let span = match is_expn_of(expr.span, "format") { + Some(s) if !s.from_expansion() => s, + _ => return, + }; + + // Operate on the only argument of `alloc::fmt::format`. + if let Some(sugg) = on_new_v1(cx, expr) { + span_useless_format(cx, span, "consider using `.to_string()`", sugg); + } else if let Some(sugg) = on_new_v1_fmt(cx, expr) { + span_useless_format(cx, span, "consider using `.to_string()`", sugg); + } + } +} + +fn span_useless_format(cx: &T, span: Span, help: &str, mut sugg: String) { + let to_replace = span.source_callsite(); + + // The callsite span contains the statement semicolon for some reason. + let snippet = snippet(cx, to_replace, ".."); + if snippet.ends_with(';') { + sugg.push(';'); + } + + span_lint_and_then(cx, USELESS_FORMAT, span, "useless use of `format!`", |diag| { + diag.span_suggestion( + to_replace, + help, + sugg, + Applicability::MachineApplicable, // snippet + ); + }); +} + +fn on_argumentv1_new<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx Expr<'_>, + arms: &'tcx [Arm<'_>], +) -> Option { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref format_args) = expr.kind; + if let ExprKind::Array(ref elems) = arms[0].body.kind; + if elems.len() == 1; + if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW); + // matches `core::fmt::Display::fmt` + if args.len() == 2; + if let ExprKind::Path(ref qpath) = args[1].kind; + if let Some(did) = cx.tables.qpath_res(qpath, args[1].hir_id).opt_def_id(); + if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD); + // check `(arg0,)` in match block + if let PatKind::Tuple(ref pats, None) = arms[0].pat.kind; + if pats.len() == 1; + then { + let ty = walk_ptrs_ty(cx.tables.pat_ty(&pats[0])); + if ty.kind != rustc_middle::ty::Str && !is_type_diagnostic_item(cx, ty, sym!(string_type)) { + return None; + } + if let ExprKind::Lit(ref lit) = format_args.kind { + if let LitKind::Str(ref s, _) = lit.node { + return Some(format!("{:?}.to_string()", s.as_str())); + } + } else { + let snip = snippet(cx, format_args.span, ""); + if let ExprKind::MethodCall(ref path, _, _) = format_args.kind { + if path.ident.name == sym!(to_string) { + return Some(format!("{}", snip)); + } + } else if let ExprKind::Binary(..) = format_args.kind { + return Some(format!("{}", snip)); + } + return Some(format!("{}.to_string()", snip)); + } + } + } + None +} + +fn on_new_v1<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> Option { + if_chain! { + if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1); + if args.len() == 2; + // Argument 1 in `new_v1()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; + if let ExprKind::Array(ref pieces) = arr.kind; + if pieces.len() == 1; + if let ExprKind::Lit(ref lit) = pieces[0].kind; + if let LitKind::Str(ref s, _) = lit.node; + // Argument 2 in `new_v1()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; + if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; + if arms.len() == 1; + if let ExprKind::Tup(ref tup) = matchee.kind; + then { + // `format!("foo")` expansion contains `match () { () => [], }` + if tup.is_empty() { + return Some(format!("{:?}.to_string()", s.as_str())); + } else if s.as_str().is_empty() { + return on_argumentv1_new(cx, &tup[0], arms); + } + } + } + None +} + +fn on_new_v1_fmt<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> Option { + if_chain! { + if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1_FORMATTED); + if args.len() == 3; + if check_unformatted(&args[2]); + // Argument 1 in `new_v1_formatted()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; + if let ExprKind::Array(ref pieces) = arr.kind; + if pieces.len() == 1; + if let ExprKind::Lit(ref lit) = pieces[0].kind; + if let LitKind::Str(..) = lit.node; + // Argument 2 in `new_v1_formatted()` + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; + if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; + if arms.len() == 1; + if let ExprKind::Tup(ref tup) = matchee.kind; + then { + return on_argumentv1_new(cx, &tup[0], arms); + } + } + None +} + +/// Checks if the expression matches +/// ```rust,ignore +/// &[_ { +/// format: _ { +/// width: _::Implied, +/// precision: _::Implied, +/// ... +/// }, +/// ..., +/// }] +/// ``` +fn check_unformatted(expr: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind; + if let ExprKind::Array(ref exprs) = expr.kind; + if exprs.len() == 1; + // struct `core::fmt::rt::v1::Argument` + if let ExprKind::Struct(_, ref fields, _) = exprs[0].kind; + if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym!(format)); + // struct `core::fmt::rt::v1::FormatSpec` + if let ExprKind::Struct(_, ref fields, _) = format_field.expr.kind; + if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym!(precision)); + if let ExprKind::Path(ref precision_path) = precision_field.expr.kind; + if last_path_segment(precision_path).ident.name == sym!(Implied); + if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym!(width)); + if let ExprKind::Path(ref width_qpath) = width_field.expr.kind; + if last_path_segment(width_qpath).ident.name == sym!(Implied); + then { + return true; + } + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/formatting.rs b/src/tools/clippy/clippy_lints/src/formatting.rs new file mode 100644 index 0000000000000..eb4b7a826f2ce --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/formatting.rs @@ -0,0 +1,326 @@ +use crate::utils::{differing_macro_contexts, snippet_opt, span_lint_and_help, span_lint_and_note}; +use if_chain::if_chain; +use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for use of the non-existent `=*`, `=!` and `=-` + /// operators. + /// + /// **Why is this bad?** This is either a typo of `*=`, `!=` or `-=` or + /// confusing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// a =- 42; // confusing, should it be `a -= 42` or `a = -42`? + /// ``` + pub SUSPICIOUS_ASSIGNMENT_FORMATTING, + style, + "suspicious formatting of `*=`, `-=` or `!=`" +} + +declare_clippy_lint! { + /// **What it does:** Checks the formatting of a unary operator on the right hand side + /// of a binary operator. It lints if there is no space between the binary and unary operators, + /// but there is a space between the unary and its operand. + /// + /// **Why is this bad?** This is either a typo in the binary operator or confusing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// if foo <- 30 { // this should be `foo < -30` but looks like a different operator + /// } + /// + /// if foo &&! bar { // this should be `foo && !bar` but looks like a different operator + /// } + /// ``` + pub SUSPICIOUS_UNARY_OP_FORMATTING, + style, + "suspicious formatting of unary `-` or `!` on the RHS of a BinOp" +} + +declare_clippy_lint! { + /// **What it does:** Checks for formatting of `else`. It lints if the `else` + /// is followed immediately by a newline or the `else` seems to be missing. + /// + /// **Why is this bad?** This is probably some refactoring remnant, even if the + /// code is correct, it might look confusing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// if foo { + /// } { // looks like an `else` is missing here + /// } + /// + /// if foo { + /// } if bar { // looks like an `else` is missing here + /// } + /// + /// if foo { + /// } else + /// + /// { // this is the `else` block of the previous `if`, but should it be? + /// } + /// + /// if foo { + /// } else + /// + /// if bar { // this is the `else` block of the previous `if`, but should it be? + /// } + /// ``` + pub SUSPICIOUS_ELSE_FORMATTING, + style, + "suspicious formatting of `else`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for possible missing comma in an array. It lints if + /// an array element is a binary operator expression and it lies on two lines. + /// + /// **Why is this bad?** This could lead to unexpected results. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let a = &[ + /// -1, -2, -3 // <= no comma here + /// -4, -5, -6 + /// ]; + /// ``` + pub POSSIBLE_MISSING_COMMA, + correctness, + "possible missing comma in array" +} + +declare_lint_pass!(Formatting => [ + SUSPICIOUS_ASSIGNMENT_FORMATTING, + SUSPICIOUS_UNARY_OP_FORMATTING, + SUSPICIOUS_ELSE_FORMATTING, + POSSIBLE_MISSING_COMMA +]); + +impl EarlyLintPass for Formatting { + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { + for w in block.stmts.windows(2) { + match (&w[0].kind, &w[1].kind) { + (&StmtKind::Expr(ref first), &StmtKind::Expr(ref second)) + | (&StmtKind::Expr(ref first), &StmtKind::Semi(ref second)) => { + check_missing_else(cx, first, second); + }, + _ => (), + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + check_assign(cx, expr); + check_unop(cx, expr); + check_else(cx, expr); + check_array(cx, expr); + } +} + +/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint. +fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind { + if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion() { + let eq_span = lhs.span.between(rhs.span); + if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind { + if let Some(eq_snippet) = snippet_opt(cx, eq_span) { + let op = UnOp::to_string(op); + let eqop_span = lhs.span.between(sub_rhs.span); + if eq_snippet.ends_with('=') { + span_lint_and_note( + cx, + SUSPICIOUS_ASSIGNMENT_FORMATTING, + eqop_span, + &format!( + "this looks like you are trying to use `.. {op}= ..`, but you \ + really are doing `.. = ({op} ..)`", + op = op + ), + None, + &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op), + ); + } + } + } + } + } +} + +/// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint. +fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) { + if_chain! { + if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind; + if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion(); + // span between BinOp LHS and RHS + let binop_span = lhs.span.between(rhs.span); + // if RHS is a UnOp + if let ExprKind::Unary(op, ref un_rhs) = rhs.kind; + // from UnOp operator to UnOp operand + let unop_operand_span = rhs.span.until(un_rhs.span); + if let Some(binop_snippet) = snippet_opt(cx, binop_span); + if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span); + let binop_str = BinOpKind::to_string(&binop.node); + // no space after BinOp operator and space after UnOp operator + if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' '); + then { + let unop_str = UnOp::to_string(op); + let eqop_span = lhs.span.between(un_rhs.span); + span_lint_and_help( + cx, + SUSPICIOUS_UNARY_OP_FORMATTING, + eqop_span, + &format!( + "by not having a space between `{binop}` and `{unop}` it looks like \ + `{binop}{unop}` is a single operator", + binop = binop_str, + unop = unop_str + ), + None, + &format!( + "put a space between `{binop}` and `{unop}` and remove the space after `{unop}`", + binop = binop_str, + unop = unop_str + ), + ); + } + } +} + +/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`. +fn check_else(cx: &EarlyContext<'_>, expr: &Expr) { + if_chain! { + if let ExprKind::If(_, then, Some(else_)) = &expr.kind; + if is_block(else_) || is_if(else_); + if !differing_macro_contexts(then.span, else_.span); + if !then.span.from_expansion() && !in_external_macro(cx.sess, expr.span); + + // workaround for rust-lang/rust#43081 + if expr.span.lo().0 != 0 && expr.span.hi().0 != 0; + + // this will be a span from the closing ‘}’ of the “then” block (excluding) to + // the “if” of the “else if” block (excluding) + let else_span = then.span.between(else_.span); + + // the snippet should look like " else \n " with maybe comments anywhere + // it’s bad when there is a ‘\n’ after the “else” + if let Some(else_snippet) = snippet_opt(cx, else_span); + if let Some(else_pos) = else_snippet.find("else"); + if else_snippet[else_pos..].contains('\n'); + let else_desc = if is_if(else_) { "if" } else { "{..}" }; + + then { + span_lint_and_note( + cx, + SUSPICIOUS_ELSE_FORMATTING, + else_span, + &format!("this is an `else {}` but the formatting might hide it", else_desc), + None, + &format!( + "to remove this lint, remove the `else` or remove the new line between \ + `else` and `{}`", + else_desc, + ), + ); + } + } +} + +#[must_use] +fn has_unary_equivalent(bin_op: BinOpKind) -> bool { + // &, *, - + bin_op == BinOpKind::And || bin_op == BinOpKind::Mul || bin_op == BinOpKind::Sub +} + +fn indentation(cx: &EarlyContext<'_>, span: Span) -> usize { + cx.sess.source_map().lookup_char_pos(span.lo()).col.0 +} + +/// Implementation of the `POSSIBLE_MISSING_COMMA` lint for array +fn check_array(cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Array(ref array) = expr.kind { + for element in array { + if_chain! { + if let ExprKind::Binary(ref op, ref lhs, _) = element.kind; + if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span); + let space_span = lhs.span.between(op.span); + if let Some(space_snippet) = snippet_opt(cx, space_span); + let lint_span = lhs.span.with_lo(lhs.span.hi()); + if space_snippet.contains('\n'); + if indentation(cx, op.span) <= indentation(cx, lhs.span); + then { + span_lint_and_note( + cx, + POSSIBLE_MISSING_COMMA, + lint_span, + "possibly missing a comma here", + None, + "to remove this lint, add a comma or write the expr in a single line", + ); + } + } + } + } +} + +fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) { + if !differing_macro_contexts(first.span, second.span) + && !first.span.from_expansion() + && is_if(first) + && (is_block(second) || is_if(second)) + { + // where the else would be + let else_span = first.span.between(second.span); + + if let Some(else_snippet) = snippet_opt(cx, else_span) { + if !else_snippet.contains('\n') { + let (looks_like, next_thing) = if is_if(second) { + ("an `else if`", "the second `if`") + } else { + ("an `else {..}`", "the next block") + }; + + span_lint_and_note( + cx, + SUSPICIOUS_ELSE_FORMATTING, + else_span, + &format!("this looks like {} but the `else` is missing", looks_like), + None, + &format!( + "to remove this lint, add the missing `else` or add a new line before {}", + next_thing, + ), + ); + } + } + } +} + +fn is_block(expr: &Expr) -> bool { + if let ExprKind::Block(..) = expr.kind { + true + } else { + false + } +} + +/// Check if the expression is an `if` or `if let` +fn is_if(expr: &Expr) -> bool { + if let ExprKind::If(..) = expr.kind { + true + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/functions.rs b/src/tools/clippy/clippy_lints/src/functions.rs new file mode 100644 index 0000000000000..c24a24733d7f3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/functions.rs @@ -0,0 +1,661 @@ +use crate::utils::{ + attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, iter_input_pats, match_def_path, + must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, span_lint_and_then, + trait_ref_of_method, type_is_unsafe_function, +}; +use rustc_ast::ast::Attribute; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit; +use rustc_hir::{def::Res, def_id::DefId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_target::spec::abi::Abi; + +declare_clippy_lint! { + /// **What it does:** Checks for functions with too many parameters. + /// + /// **Why is this bad?** Functions with lots of parameters are considered bad + /// style and reduce readability (“what does the 5th parameter mean?”). Consider + /// grouping some parameters into a new type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct Color; + /// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) { + /// // .. + /// } + /// ``` + pub TOO_MANY_ARGUMENTS, + complexity, + "functions with too many arguments" +} + +declare_clippy_lint! { + /// **What it does:** Checks for functions with a large amount of lines. + /// + /// **Why is this bad?** Functions with a lot of lines are harder to understand + /// due to having to look at a larger amount of code to understand what the + /// function is doing. Consider splitting the body of the function into + /// multiple functions. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ``` rust + /// fn im_too_long() { + /// println!(""); + /// // ... 100 more LoC + /// println!(""); + /// } + /// ``` + pub TOO_MANY_LINES, + pedantic, + "functions with too many lines" +} + +declare_clippy_lint! { + /// **What it does:** Checks for public functions that dereference raw pointer + /// arguments but are not marked unsafe. + /// + /// **Why is this bad?** The function should probably be marked `unsafe`, since + /// for an arbitrary raw pointer, there is no way of telling for sure if it is + /// valid. + /// + /// **Known problems:** + /// + /// * It does not check functions recursively so if the pointer is passed to a + /// private non-`unsafe` function which does the dereferencing, the lint won't + /// trigger. + /// * It only checks for arguments whose type are raw pointers, not raw pointers + /// got from an argument in some other way (`fn foo(bar: &[*const u8])` or + /// `some_argument.get_raw_ptr()`). + /// + /// **Example:** + /// ```rust + /// pub fn foo(x: *const u8) { + /// println!("{}", unsafe { *x }); + /// } + /// ``` + pub NOT_UNSAFE_PTR_ARG_DEREF, + correctness, + "public functions dereferencing raw pointer arguments but not marked `unsafe`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for a [`#[must_use]`] attribute on + /// unit-returning functions and methods. + /// + /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// + /// **Why is this bad?** Unit values are useless. The attribute is likely + /// a remnant of a refactoring that removed the return type. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// #[must_use] + /// fn useless() { } + /// ``` + pub MUST_USE_UNIT, + style, + "`#[must_use]` attribute on a unit-returning function / method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for a [`#[must_use]`] attribute without + /// further information on functions and methods that return a type already + /// marked as `#[must_use]`. + /// + /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// + /// **Why is this bad?** The attribute isn't needed. Not using the result + /// will already be reported. Alternatively, one can add some text to the + /// attribute to improve the lint message. + /// + /// **Known problems:** None. + /// + /// **Examples:** + /// ```rust + /// #[must_use] + /// fn double_must_use() -> Result<(), ()> { + /// unimplemented!(); + /// } + /// ``` + pub DOUBLE_MUST_USE, + style, + "`#[must_use]` attribute on a `#[must_use]`-returning function / method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for public functions that have no + /// [`#[must_use]`] attribute, but return something not already marked + /// must-use, have no mutable arg and mutate no statics. + /// + /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// + /// **Why is this bad?** Not bad at all, this lint just shows places where + /// you could add the attribute. + /// + /// **Known problems:** The lint only checks the arguments for mutable + /// types without looking if they are actually changed. On the other hand, + /// it also ignores a broad range of potentially interesting side effects, + /// because we cannot decide whether the programmer intends the function to + /// be called for the side effect or the result. Expect many false + /// positives. At least we don't lint if the result type is unit or already + /// `#[must_use]`. + /// + /// **Examples:** + /// ```rust + /// // this could be annotated with `#[must_use]`. + /// fn id(t: T) -> T { t } + /// ``` + pub MUST_USE_CANDIDATE, + pedantic, + "function or method that could take a `#[must_use]` attribute" +} + +#[derive(Copy, Clone)] +pub struct Functions { + threshold: u64, + max_lines: u64, +} + +impl Functions { + pub fn new(threshold: u64, max_lines: u64) -> Self { + Self { threshold, max_lines } + } +} + +impl_lint_pass!(Functions => [ + TOO_MANY_ARGUMENTS, + TOO_MANY_LINES, + NOT_UNSAFE_PTR_ARG_DEREF, + MUST_USE_UNIT, + DOUBLE_MUST_USE, + MUST_USE_CANDIDATE, +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Functions { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: intravisit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + body: &'tcx hir::Body<'_>, + span: Span, + hir_id: hir::HirId, + ) { + let unsafety = match kind { + intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _, _) => unsafety, + intravisit::FnKind::Method(_, sig, _, _) => sig.header.unsafety, + intravisit::FnKind::Closure(_) => return, + }; + + // don't warn for implementations, it's not their fault + if !is_trait_impl_item(cx, hir_id) { + // don't lint extern functions decls, it's not their fault either + match kind { + intravisit::FnKind::Method( + _, + &hir::FnSig { + header: hir::FnHeader { abi: Abi::Rust, .. }, + .. + }, + _, + _, + ) + | intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _, _) => { + self.check_arg_number(cx, decl, span.with_hi(decl.output.span().hi())) + }, + _ => {}, + } + } + + Self::check_raw_ptr(cx, unsafety, decl, body, hir_id); + self.check_line_number(cx, span, body); + } + + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + let attr = must_use_attr(&item.attrs); + if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind { + if let Some(attr) = attr { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr); + return; + } + if cx.access_levels.is_exported(item.hir_id) + && !is_proc_macro(&item.attrs) + && attr_by_name(&item.attrs, "no_mangle").is_none() + { + check_must_use_candidate( + cx, + &sig.decl, + cx.tcx.hir().body(*body_id), + item.span, + item.hir_id, + item.span.with_hi(sig.decl.output.span().hi()), + "this function could have a `#[must_use]` attribute", + ); + } + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'_>) { + if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { + let attr = must_use_attr(&item.attrs); + if let Some(attr) = attr { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr); + } else if cx.access_levels.is_exported(item.hir_id) + && !is_proc_macro(&item.attrs) + && trait_ref_of_method(cx, item.hir_id).is_none() + { + check_must_use_candidate( + cx, + &sig.decl, + cx.tcx.hir().body(*body_id), + item.span, + item.hir_id, + item.span.with_hi(sig.decl.output.span().hi()), + "this method could have a `#[must_use]` attribute", + ); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'_>) { + if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { + // don't lint extern functions decls, it's not their fault + if sig.header.abi == Abi::Rust { + self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi())); + } + + let attr = must_use_attr(&item.attrs); + if let Some(attr) = attr { + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr); + } + if let hir::TraitFn::Provided(eid) = *eid { + let body = cx.tcx.hir().body(eid); + Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id); + + if attr.is_none() && cx.access_levels.is_exported(item.hir_id) && !is_proc_macro(&item.attrs) { + check_must_use_candidate( + cx, + &sig.decl, + body, + item.span, + item.hir_id, + item.span.with_hi(sig.decl.output.span().hi()), + "this method could have a `#[must_use]` attribute", + ); + } + } + } + } +} + +impl<'a, 'tcx> Functions { + fn check_arg_number(self, cx: &LateContext<'_, '_>, decl: &hir::FnDecl<'_>, fn_span: Span) { + let args = decl.inputs.len() as u64; + if args > self.threshold { + span_lint( + cx, + TOO_MANY_ARGUMENTS, + fn_span, + &format!("this function has too many arguments ({}/{})", args, self.threshold), + ); + } + } + + fn check_line_number(self, cx: &LateContext<'_, '_>, span: Span, body: &'tcx hir::Body<'_>) { + if in_external_macro(cx.sess(), span) { + return; + } + + let code_snippet = snippet(cx, body.value.span, ".."); + let mut line_count: u64 = 0; + let mut in_comment = false; + let mut code_in_line; + + // Skip the surrounding function decl. + let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1); + let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len()); + let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines(); + + for mut line in function_lines { + code_in_line = false; + loop { + line = line.trim_start(); + if line.is_empty() { + break; + } + if in_comment { + match line.find("*/") { + Some(i) => { + line = &line[i + 2..]; + in_comment = false; + continue; + }, + None => break, + } + } else { + let multi_idx = line.find("/*").unwrap_or_else(|| line.len()); + let single_idx = line.find("//").unwrap_or_else(|| line.len()); + code_in_line |= multi_idx > 0 && single_idx > 0; + // Implies multi_idx is below line.len() + if multi_idx < single_idx { + line = &line[multi_idx + 2..]; + in_comment = true; + continue; + } + break; + } + } + if code_in_line { + line_count += 1; + } + } + + if line_count > self.max_lines { + span_lint(cx, TOO_MANY_LINES, span, "This function has a large number of lines.") + } + } + + fn check_raw_ptr( + cx: &LateContext<'a, 'tcx>, + unsafety: hir::Unsafety, + decl: &'tcx hir::FnDecl<'_>, + body: &'tcx hir::Body<'_>, + hir_id: hir::HirId, + ) { + let expr = &body.value; + if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) { + let raw_ptrs = iter_input_pats(decl, body) + .zip(decl.inputs.iter()) + .filter_map(|(arg, ty)| raw_ptr_arg(arg, ty)) + .collect::>(); + + if !raw_ptrs.is_empty() { + let tables = cx.tcx.body_tables(body.id()); + let mut v = DerefVisitor { + cx, + ptrs: raw_ptrs, + tables, + }; + + intravisit::walk_expr(&mut v, expr); + } + } + } +} + +fn check_needless_must_use( + cx: &LateContext<'_, '_>, + decl: &hir::FnDecl<'_>, + item_id: hir::HirId, + item_span: Span, + fn_header_span: Span, + attr: &Attribute, +) { + if in_external_macro(cx.sess(), item_span) { + return; + } + if returns_unit(decl) { + span_lint_and_then( + cx, + MUST_USE_UNIT, + fn_header_span, + "this unit-returning function has a `#[must_use]` attribute", + |diag| { + diag.span_suggestion( + attr.span, + "remove the attribute", + "".into(), + Applicability::MachineApplicable, + ); + }, + ); + } else if !attr.is_value_str() && is_must_use_ty(cx, return_ty(cx, item_id)) { + span_lint_and_help( + cx, + DOUBLE_MUST_USE, + fn_header_span, + "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`", + None, + "either add some descriptive text or remove the attribute", + ); + } +} + +fn check_must_use_candidate<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + decl: &'tcx hir::FnDecl<'_>, + body: &'tcx hir::Body<'_>, + item_span: Span, + item_id: hir::HirId, + fn_span: Span, + msg: &str, +) { + if has_mutable_arg(cx, body) + || mutates_static(cx, body) + || in_external_macro(cx.sess(), item_span) + || returns_unit(decl) + || !cx.access_levels.is_exported(item_id) + || is_must_use_ty(cx, return_ty(cx, item_id)) + { + return; + } + span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { + if let Some(snippet) = snippet_opt(cx, fn_span) { + diag.span_suggestion( + fn_span, + "add the attribute", + format!("#[must_use] {}", snippet), + Applicability::MachineApplicable, + ); + } + }); +} + +fn returns_unit(decl: &hir::FnDecl<'_>) -> bool { + match decl.output { + hir::FnRetTy::DefaultReturn(_) => true, + hir::FnRetTy::Return(ref ty) => match ty.kind { + hir::TyKind::Tup(ref tys) => tys.is_empty(), + hir::TyKind::Never => true, + _ => false, + }, + } +} + +fn has_mutable_arg(cx: &LateContext<'_, '_>, body: &hir::Body<'_>) -> bool { + let mut tys = FxHashSet::default(); + body.params.iter().any(|param| is_mutable_pat(cx, ¶m.pat, &mut tys)) +} + +fn is_mutable_pat(cx: &LateContext<'_, '_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet) -> bool { + if let hir::PatKind::Wild = pat.kind { + return false; // ignore `_` patterns + } + let def_id = pat.hir_id.owner.to_def_id(); + if cx.tcx.has_typeck_tables(def_id) { + is_mutable_ty( + cx, + &cx.tcx.typeck_tables_of(def_id.expect_local()).pat_ty(pat), + pat.span, + tys, + ) + } else { + false + } +} + +static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]]; + +fn is_mutable_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet) -> bool { + match ty.kind { + // primitive types are never mutable + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false, + ty::Adt(ref adt, ref substs) => { + tys.insert(adt.did) && !ty.is_freeze(cx.tcx, cx.param_env, span) + || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path)) + && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)) + }, + ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)), + ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys), + ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { + mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys) + }, + // calling something constitutes a side effect, so return true on all callables + // also never calls need not be used, so return true for them, too + _ => true, + } +} + +fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option { + if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) { + Some(id) + } else { + None + } +} + +struct DerefVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + ptrs: FxHashSet, + tables: &'a ty::TypeckTables<'tcx>, +} + +impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + match expr.kind { + hir::ExprKind::Call(ref f, args) => { + let ty = self.tables.expr_ty(f); + + if type_is_unsafe_function(self.cx, ty) { + for arg in args { + self.check_arg(arg); + } + } + }, + hir::ExprKind::MethodCall(_, _, args) => { + let def_id = self.tables.type_dependent_def_id(expr.hir_id).unwrap(); + let base_type = self.cx.tcx.type_of(def_id); + + if type_is_unsafe_function(self.cx, base_type) { + for arg in args { + self.check_arg(arg); + } + } + }, + hir::ExprKind::Unary(hir::UnOp::UnDeref, ref ptr) => self.check_arg(ptr), + _ => (), + } + + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} + +impl<'a, 'tcx> DerefVisitor<'a, 'tcx> { + fn check_arg(&self, ptr: &hir::Expr<'_>) { + if let hir::ExprKind::Path(ref qpath) = ptr.kind { + if let Res::Local(id) = qpath_res(self.cx, qpath, ptr.hir_id) { + if self.ptrs.contains(&id) { + span_lint( + self.cx, + NOT_UNSAFE_PTR_ARG_DEREF, + ptr.span, + "this public function dereferences a raw pointer but is not marked `unsafe`", + ); + } + } + } + } +} + +struct StaticMutVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + mutates_static: bool, +} + +impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall}; + + if self.mutates_static { + return; + } + match expr.kind { + Call(_, args) | MethodCall(_, _, args) => { + let mut tys = FxHashSet::default(); + for arg in args { + let def_id = arg.hir_id.owner.to_def_id(); + if self.cx.tcx.has_typeck_tables(def_id) + && is_mutable_ty( + self.cx, + self.cx.tcx.typeck_tables_of(def_id.expect_local()).expr_ty(arg), + arg.span, + &mut tys, + ) + && is_mutated_static(self.cx, arg) + { + self.mutates_static = true; + return; + } + tys.clear(); + } + }, + Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => { + self.mutates_static |= is_mutated_static(self.cx, target) + }, + _ => {}, + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} + +fn is_mutated_static(cx: &LateContext<'_, '_>, e: &hir::Expr<'_>) -> bool { + use hir::ExprKind::{Field, Index, Path}; + + match e.kind { + Path(ref qpath) => { + if let Res::Local(_) = qpath_res(cx, qpath, e.hir_id) { + false + } else { + true + } + }, + Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(cx, inner), + _ => false, + } +} + +fn mutates_static<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, body: &'tcx hir::Body<'_>) -> bool { + let mut v = StaticMutVisitor { + cx, + mutates_static: false, + }; + intravisit::walk_expr(&mut v, &body.value); + v.mutates_static +} diff --git a/src/tools/clippy/clippy_lints/src/future_not_send.rs b/src/tools/clippy/clippy_lints/src/future_not_send.rs new file mode 100644 index 0000000000000..704a95ec0a090 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/future_not_send.rs @@ -0,0 +1,110 @@ +use crate::utils; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, FnDecl, HirId}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{Opaque, Predicate::Trait, ToPolyTraitRef}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, Span}; +use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt; +use rustc_trait_selection::traits::{self, FulfillmentError, TraitEngine}; + +declare_clippy_lint! { + /// **What it does:** This lint requires Future implementations returned from + /// functions and methods to implement the `Send` marker trait. It is mostly + /// used by library authors (public and internal) that target an audience where + /// multithreaded executors are likely to be used for running these Futures. + /// + /// **Why is this bad?** A Future implementation captures some state that it + /// needs to eventually produce its final value. When targeting a multithreaded + /// executor (which is the norm on non-embedded devices) this means that this + /// state may need to be transported to other threads, in other words the + /// whole Future needs to implement the `Send` marker trait. If it does not, + /// then the resulting Future cannot be submitted to a thread pool in the + /// end user’s code. + /// + /// Especially for generic functions it can be confusing to leave the + /// discovery of this problem to the end user: the reported error location + /// will be far from its cause and can in many cases not even be fixed without + /// modifying the library where the offending Future implementation is + /// produced. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// async fn not_send(bytes: std::rc::Rc<[u8]>) {} + /// ``` + /// Use instead: + /// ```rust + /// async fn is_send(bytes: std::sync::Arc<[u8]>) {} + /// ``` + pub FUTURE_NOT_SEND, + nursery, + "public Futures must be Send" +} + +declare_lint_pass!(FutureNotSend => [FUTURE_NOT_SEND]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FutureNotSend { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'tcx>, + _: &'tcx Body<'tcx>, + _: Span, + hir_id: HirId, + ) { + if let FnKind::Closure(_) = kind { + return; + } + let ret_ty = utils::return_ty(cx, hir_id); + if let Opaque(id, subst) = ret_ty.kind { + let preds = cx.tcx.predicates_of(id).instantiate(cx.tcx, subst); + let mut is_future = false; + for p in preds.predicates { + if let Some(trait_ref) = p.to_opt_poly_trait_ref() { + if Some(trait_ref.def_id()) == cx.tcx.lang_items().future_trait() { + is_future = true; + break; + } + } + } + if is_future { + let send_trait = cx.tcx.get_diagnostic_item(sym::send_trait).unwrap(); + let span = decl.output.span(); + let send_result = cx.tcx.infer_ctxt().enter(|infcx| { + let cause = traits::ObligationCause::misc(span, hir_id); + let mut fulfillment_cx = traits::FulfillmentContext::new(); + fulfillment_cx.register_bound(&infcx, cx.param_env, ret_ty, send_trait, cause); + fulfillment_cx.select_all_or_error(&infcx) + }); + if let Err(send_errors) = send_result { + utils::span_lint_and_then( + cx, + FUTURE_NOT_SEND, + span, + "future cannot be sent between threads safely", + |db| { + cx.tcx.infer_ctxt().enter(|infcx| { + for FulfillmentError { obligation, .. } in send_errors { + infcx.maybe_note_obligation_cause_for_async_await(db, &obligation); + if let Trait(trait_pred, _) = obligation.predicate { + let trait_ref = trait_pred.to_poly_trait_ref(); + db.note(&*format!( + "`{}` doesn't implement `{}`", + trait_ref.self_ty(), + trait_ref.print_only_trait_path(), + )); + } + } + }) + }, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/get_last_with_len.rs b/src/tools/clippy/clippy_lints/src/get_last_with_len.rs new file mode 100644 index 0000000000000..c32e0a2290d12 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/get_last_with_len.rs @@ -0,0 +1,103 @@ +//! lint on using `x.get(x.len() - 1)` instead of `x.last()` + +use crate::utils::{is_type_diagnostic_item, snippet_with_applicability, span_lint_and_sugg, SpanlessEq}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +declare_clippy_lint! { + /// **What it does:** Checks for using `x.get(x.len() - 1)` instead of + /// `x.last()`. + /// + /// **Why is this bad?** Using `x.last()` is easier to read and has the same + /// result. + /// + /// Note that using `x[x.len() - 1]` is semantically different from + /// `x.last()`. Indexing into the array will panic on out-of-bounds + /// accesses, while `x.get()` and `x.last()` will return `None`. + /// + /// There is another lint (get_unwrap) that covers the case of using + /// `x.get(index).unwrap()` instead of `x[index]`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let x = vec![2, 3, 5]; + /// let last_element = x.get(x.len() - 1); + /// + /// // Good + /// let x = vec![2, 3, 5]; + /// let last_element = x.last(); + /// ``` + pub GET_LAST_WITH_LEN, + complexity, + "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler" +} + +declare_lint_pass!(GetLastWithLen => [GET_LAST_WITH_LEN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for GetLastWithLen { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + // Is a method call + if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind; + + // Method name is "get" + if path.ident.name == sym!(get); + + // Argument 0 (the struct we're calling the method on) is a vector + if let Some(struct_calling_on) = args.get(0); + let struct_ty = cx.tables.expr_ty(struct_calling_on); + if is_type_diagnostic_item(cx, struct_ty, sym!(vec_type)); + + // Argument to "get" is a subtraction + if let Some(get_index_arg) = args.get(1); + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, + .. + }, + lhs, + rhs, + ) = &get_index_arg.kind; + + // LHS of subtraction is "x.len()" + if let ExprKind::MethodCall(arg_lhs_path, _, lhs_args) = &lhs.kind; + if arg_lhs_path.ident.name == sym!(len); + if let Some(arg_lhs_struct) = lhs_args.get(0); + + // The two vectors referenced (x in x.get(...) and in x.len()) + if SpanlessEq::new(cx).eq_expr(struct_calling_on, arg_lhs_struct); + + // RHS of subtraction is 1 + if let ExprKind::Lit(rhs_lit) = &rhs.kind; + if let LitKind::Int(1, ..) = rhs_lit.node; + + then { + let mut applicability = Applicability::MachineApplicable; + let vec_name = snippet_with_applicability( + cx, + struct_calling_on.span, "vec", + &mut applicability, + ); + + span_lint_and_sugg( + cx, + GET_LAST_WITH_LEN, + expr.span, + &format!("accessing last element with `{0}.get({0}.len() - 1)`", vec_name), + "try", + format!("{}.last()", vec_name), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/identity_conversion.rs b/src/tools/clippy/clippy_lints/src/identity_conversion.rs new file mode 100644 index 0000000000000..33a9478f05883 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/identity_conversion.rs @@ -0,0 +1,124 @@ +use crate::utils::{ + match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, span_lint_and_sugg, +}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for always-identical `Into`/`From`/`IntoIter` conversions. + /// + /// **Why is this bad?** Redundant code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // format!() returns a `String` + /// let s: String = format!("hello").into(); + /// ``` + pub IDENTITY_CONVERSION, + complexity, + "using always-identical `Into`/`From`/`IntoIter` conversions" +} + +#[derive(Default)] +pub struct IdentityConversion { + try_desugar_arm: Vec, +} + +impl_lint_pass!(IdentityConversion => [IDENTITY_CONVERSION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityConversion { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if Some(&e.hir_id) == self.try_desugar_arm.last() { + return; + } + + match e.kind { + ExprKind::Match(_, ref arms, MatchSource::TryDesugar) => { + let e = match arms[0].body.kind { + ExprKind::Ret(Some(ref e)) | ExprKind::Break(_, Some(ref e)) => e, + _ => return, + }; + if let ExprKind::Call(_, ref args) = e.kind { + self.try_desugar_arm.push(args[0].hir_id); + } + }, + + ExprKind::MethodCall(ref name, .., ref args) => { + if match_trait_method(cx, e, &paths::INTO) && &*name.ident.as_str() == "into" { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if same_tys(cx, a, b) { + let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); + + span_lint_and_sugg( + cx, + IDENTITY_CONVERSION, + e.span, + "identical conversion", + "consider removing `.into()`", + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + if match_trait_method(cx, e, &paths::INTO_ITERATOR) && &*name.ident.as_str() == "into_iter" { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if same_tys(cx, a, b) { + let sugg = snippet(cx, args[0].span, "").into_owned(); + span_lint_and_sugg( + cx, + IDENTITY_CONVERSION, + e.span, + "identical conversion", + "consider removing `.into_iter()`", + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + }, + + ExprKind::Call(ref path, ref args) => { + if let ExprKind::Path(ref qpath) = path.kind { + if let Some(def_id) = cx.tables.qpath_res(qpath, path.hir_id).opt_def_id() { + if match_def_path(cx, def_id, &paths::FROM_FROM) { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if same_tys(cx, a, b) { + let sugg = snippet(cx, args[0].span.source_callsite(), "").into_owned(); + let sugg_msg = + format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); + span_lint_and_sugg( + cx, + IDENTITY_CONVERSION, + e.span, + "identical conversion", + &sugg_msg, + sugg, + Applicability::MachineApplicable, // snippet + ); + } + } + } + } + }, + + _ => {}, + } + } + + fn check_expr_post(&mut self, _: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if Some(&e.hir_id) == self.try_desugar_arm.last() { + self.try_desugar_arm.pop(); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/identity_op.rs b/src/tools/clippy/clippy_lints/src/identity_op.rs new file mode 100644 index 0000000000000..088e4ab1921fb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/identity_op.rs @@ -0,0 +1,82 @@ +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +use crate::consts::{constant_simple, Constant}; +use crate::utils::{clip, snippet, span_lint, unsext}; + +declare_clippy_lint! { + /// **What it does:** Checks for identity operations, e.g., `x + 0`. + /// + /// **Why is this bad?** This code can be removed without changing the + /// meaning. So it just obscures what's going on. Delete it mercilessly. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// x / 1 + 0 * 1 - 0 | 0; + /// ``` + pub IDENTITY_OP, + complexity, + "using identity operations, e.g., `x + 0` or `y / 1`" +} + +declare_lint_pass!(IdentityOp => [IDENTITY_OP]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityOp { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + if let ExprKind::Binary(ref cmp, ref left, ref right) = e.kind { + match cmp.node { + BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { + check(cx, left, 0, e.span, right.span); + check(cx, right, 0, e.span, left.span); + }, + BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => check(cx, right, 0, e.span, left.span), + BinOpKind::Mul => { + check(cx, left, 1, e.span, right.span); + check(cx, right, 1, e.span, left.span); + }, + BinOpKind::Div => check(cx, right, 1, e.span, left.span), + BinOpKind::BitAnd => { + check(cx, left, -1, e.span, right.span); + check(cx, right, -1, e.span, left.span); + }, + _ => (), + } + } + } +} + +#[allow(clippy::cast_possible_wrap)] +fn check(cx: &LateContext<'_, '_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) { + if let Some(Constant::Int(v)) = constant_simple(cx, cx.tables, e) { + let check = match cx.tables.expr_ty(e).kind { + ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), + ty::Uint(uty) => clip(cx.tcx, !0, uty), + _ => return, + }; + if match m { + 0 => v == 0, + -1 => v == check, + 1 => v == 1, + _ => unreachable!(), + } { + span_lint( + cx, + IDENTITY_OP, + span, + &format!( + "the operation is ineffective. Consider reducing it to `{}`", + snippet(cx, arg, "..") + ), + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/if_let_mutex.rs b/src/tools/clippy/clippy_lints/src/if_let_mutex.rs new file mode 100644 index 0000000000000..ae92a96d16347 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/if_let_mutex.rs @@ -0,0 +1,160 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_help, SpanlessEq}; +use if_chain::if_chain; +use rustc_hir::intravisit::{self as visit, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, ExprKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `Mutex::lock` calls in `if let` expression + /// with lock calls in any of the else blocks. + /// + /// **Why is this bad?** The Mutex lock remains held for the whole + /// `if let ... else` block and deadlocks. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// if let Ok(thing) = mutex.lock() { + /// do_thing(); + /// } else { + /// mutex.lock(); + /// } + /// ``` + /// Should be written + /// ```rust,ignore + /// let locked = mutex.lock(); + /// if let Ok(thing) = locked { + /// do_thing(thing); + /// } else { + /// use_locked(locked); + /// } + /// ``` + pub IF_LET_MUTEX, + correctness, + "locking a `Mutex` in an `if let` block can cause deadlocks" +} + +declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]); + +impl LateLintPass<'_, '_> for IfLetMutex { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, ex: &'_ Expr<'_>) { + let mut arm_visit = ArmVisitor { + mutex_lock_called: false, + found_mutex: None, + cx, + }; + let mut op_visit = OppVisitor { + mutex_lock_called: false, + found_mutex: None, + cx, + }; + if let ExprKind::Match( + ref op, + ref arms, + MatchSource::IfLetDesugar { + contains_else_clause: true, + }, + ) = ex.kind + { + op_visit.visit_expr(op); + if op_visit.mutex_lock_called { + for arm in *arms { + arm_visit.visit_arm(arm); + } + + if arm_visit.mutex_lock_called && arm_visit.same_mutex(cx, op_visit.found_mutex.unwrap()) { + span_lint_and_help( + cx, + IF_LET_MUTEX, + ex.span, + "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock", + None, + "move the lock call outside of the `if let ...` expression", + ); + } + } + } + } +} + +/// Checks if `Mutex::lock` is called in the `if let _ = expr. +pub struct OppVisitor<'tcx, 'l> { + mutex_lock_called: bool, + found_mutex: Option<&'tcx Expr<'tcx>>, + cx: &'tcx LateContext<'tcx, 'l>, +} + +impl<'tcx, 'l> Visitor<'tcx> for OppVisitor<'tcx, 'l> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if let Some(mutex) = is_mutex_lock_call(self.cx, expr); + then { + self.found_mutex = Some(mutex); + self.mutex_lock_called = true; + return; + } + } + visit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Checks if `Mutex::lock` is called in any of the branches. +pub struct ArmVisitor<'tcx, 'l> { + mutex_lock_called: bool, + found_mutex: Option<&'tcx Expr<'tcx>>, + cx: &'tcx LateContext<'tcx, 'l>, +} + +impl<'tcx, 'l> Visitor<'tcx> for ArmVisitor<'tcx, 'l> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if_chain! { + if let Some(mutex) = is_mutex_lock_call(self.cx, expr); + then { + self.found_mutex = Some(mutex); + self.mutex_lock_called = true; + return; + } + } + visit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +impl<'tcx, 'l> ArmVisitor<'tcx, 'l> { + fn same_mutex(&self, cx: &LateContext<'_, '_>, op_mutex: &Expr<'_>) -> bool { + if let Some(arm_mutex) = self.found_mutex { + SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex) + } else { + false + } + } +} + +fn is_mutex_lock_call<'a>(cx: &LateContext<'a, '_>, expr: &'a Expr<'_>) -> Option<&'a Expr<'a>> { + if_chain! { + if let ExprKind::MethodCall(path, _span, args) = &expr.kind; + if path.ident.to_string() == "lock"; + let ty = cx.tables.expr_ty(&args[0]); + if is_type_diagnostic_item(cx, ty, sym!(mutex_type)); + then { + Some(&args[0]) + } else { + None + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/if_let_some_result.rs b/src/tools/clippy/clippy_lints/src/if_let_some_result.rs new file mode 100644 index 0000000000000..9b13f7609247a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/if_let_some_result.rs @@ -0,0 +1,72 @@ +use crate::utils::{is_type_diagnostic_item, method_chain_args, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, MatchSource, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:*** Checks for unnecessary `ok()` in if let. + /// + /// **Why is this bad?** Calling `ok()` in if let is unnecessary, instead match + /// on `Ok(pat)` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for i in iter { + /// if let Some(value) = i.parse().ok() { + /// vec.push(value) + /// } + /// } + /// ``` + /// Could be written: + /// + /// ```ignore + /// for i in iter { + /// if let Ok(value) = i.parse() { + /// vec.push(value) + /// } + /// } + /// ``` + pub IF_LET_SOME_RESULT, + style, + "usage of `ok()` in `if let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead" +} + +declare_lint_pass!(OkIfLet => [IF_LET_SOME_RESULT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OkIfLet { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { //begin checking variables + if let ExprKind::Match(ref op, ref body, source) = expr.kind; //test if expr is a match + if let MatchSource::IfLetDesugar { .. } = source; //test if it is an If Let + if let ExprKind::MethodCall(_, ok_span, ref result_types) = op.kind; //check is expr.ok() has type Result.ok() + if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = body[0].pat.kind; //get operation + if method_chain_args(op, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized; + if is_type_diagnostic_item(cx, cx.tables.expr_ty(&result_types[0]), sym!(result_type)); + if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some"; + + then { + let mut applicability = Applicability::MachineApplicable; + let some_expr_string = snippet_with_applicability(cx, y[0].span, "", &mut applicability); + let trimmed_ok = snippet_with_applicability(cx, op.span.until(ok_span), "", &mut applicability); + let sugg = format!( + "if let Ok({}) = {}", + some_expr_string, + trimmed_ok.trim().trim_end_matches('.'), + ); + span_lint_and_sugg( + cx, + IF_LET_SOME_RESULT, + expr.span.with_hi(op.span.hi()), + "Matching on `Some` with `ok()` is redundant", + &format!("Consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string), + sugg, + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/if_not_else.rs b/src/tools/clippy/clippy_lints/src/if_not_else.rs new file mode 100644 index 0000000000000..c11e291f98e4b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/if_not_else.rs @@ -0,0 +1,83 @@ +//! lint on if branches that could be swapped so no `!` operation is necessary +//! on the condition + +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `!` or `!=` in an if condition with an + /// else branch. + /// + /// **Why is this bad?** Negations reduce the readability of statements. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v: Vec = vec![]; + /// # fn a() {} + /// # fn b() {} + /// if !v.is_empty() { + /// a() + /// } else { + /// b() + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # let v: Vec = vec![]; + /// # fn a() {} + /// # fn b() {} + /// if v.is_empty() { + /// b() + /// } else { + /// a() + /// } + /// ``` + pub IF_NOT_ELSE, + pedantic, + "`if` branches that could be swapped so no negation operation is necessary on the condition" +} + +declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]); + +impl EarlyLintPass for IfNotElse { + fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) { + if in_external_macro(cx.sess(), item.span) { + return; + } + if let ExprKind::If(ref cond, _, Some(ref els)) = item.kind { + if let ExprKind::Block(..) = els.kind { + match cond.kind { + ExprKind::Unary(UnOp::Not, _) => { + span_lint_and_help( + cx, + IF_NOT_ELSE, + item.span, + "Unnecessary boolean `not` operation", + None, + "remove the `!` and swap the blocks of the `if`/`else`", + ); + }, + ExprKind::Binary(ref kind, _, _) if kind.node == BinOpKind::Ne => { + span_lint_and_help( + cx, + IF_NOT_ELSE, + item.span, + "Unnecessary `!=` operation", + None, + "change to `==` and swap the blocks of the `if`/`else`", + ); + }, + _ => (), + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/implicit_return.rs b/src/tools/clippy/clippy_lints/src/implicit_return.rs new file mode 100644 index 0000000000000..c4308fd26a302 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/implicit_return.rs @@ -0,0 +1,150 @@ +use crate::utils::{ + fn_has_unsatisfiable_preds, match_def_path, + paths::{BEGIN_PANIC, BEGIN_PANIC_FMT}, + snippet_opt, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for missing return statements at the end of a block. + /// + /// **Why is this bad?** Actually omitting the return keyword is idiomatic Rust code. Programmers + /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss + /// the last returning statement because the only difference is a missing `;`. Especially in bigger + /// code with multiple return paths having a `return` keyword makes it easier to find the + /// corresponding statements. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + /// add return + /// ```rust + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + pub IMPLICIT_RETURN, + restriction, + "use a return statement like `return expr` instead of an expression" +} + +declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); + +static LINT_BREAK: &str = "change `break` to `return` as shown"; +static LINT_RETURN: &str = "add `return` as shown"; + +fn lint(cx: &LateContext<'_, '_>, outer_span: Span, inner_span: Span, msg: &str) { + let outer_span = outer_span.source_callsite(); + let inner_span = inner_span.source_callsite(); + + span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing `return` statement", |diag| { + if let Some(snippet) = snippet_opt(cx, inner_span) { + diag.span_suggestion( + outer_span, + msg, + format!("return {}", snippet), + Applicability::MachineApplicable, + ); + } + }); +} + +fn expr_match(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + match expr.kind { + // loops could be using `break` instead of `return` + ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => { + if let Some(expr) = &block.expr { + expr_match(cx, expr); + } + // only needed in the case of `break` with `;` at the end + else if let Some(stmt) = block.stmts.last() { + if_chain! { + if let StmtKind::Semi(expr, ..) = &stmt.kind; + // make sure it's a break, otherwise we want to skip + if let ExprKind::Break(.., break_expr) = &expr.kind; + if let Some(break_expr) = break_expr; + then { + lint(cx, expr.span, break_expr.span, LINT_BREAK); + } + } + } + }, + // use `return` instead of `break` + ExprKind::Break(.., break_expr) => { + if let Some(break_expr) = break_expr { + lint(cx, expr.span, break_expr.span, LINT_BREAK); + } + }, + ExprKind::Match(.., arms, source) => { + let check_all_arms = match source { + MatchSource::IfLetDesugar { + contains_else_clause: has_else, + } => has_else, + _ => true, + }; + + if check_all_arms { + for arm in arms { + expr_match(cx, &arm.body); + } + } else { + expr_match(cx, &arms.first().expect("`if let` doesn't have a single arm").body); + } + }, + // skip if it already has a return statement + ExprKind::Ret(..) => (), + // make sure it's not a call that panics + ExprKind::Call(expr, ..) => { + if_chain! { + if let ExprKind::Path(qpath) = &expr.kind; + if let Some(path_def_id) = cx.tables.qpath_res(qpath, expr.hir_id).opt_def_id(); + if match_def_path(cx, path_def_id, &BEGIN_PANIC) || + match_def_path(cx, path_def_id, &BEGIN_PANIC_FMT); + then { } + else { + lint(cx, expr.span, expr.span, LINT_RETURN) + } + } + }, + // everything else is missing `return` + _ => lint(cx, expr.span, expr.span, LINT_RETURN), + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitReturn { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + _: HirId, + ) { + let def_id = cx.tcx.hir().body_owner_def_id(body.id()); + + // Building MIR for `fn`s with unsatisfiable preds results in ICE. + if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { + return; + } + + let mir = cx.tcx.optimized_mir(def_id.to_def_id()); + + // checking return type through MIR, HIR is not able to determine inferred closure return types + // make sure it's not a macro + if !mir.return_ty().is_unit() && !span.from_expansion() { + expr_match(cx, &body.value); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs new file mode 100644 index 0000000000000..155a93de4facf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs @@ -0,0 +1,173 @@ +use crate::utils::{higher, in_macro, match_qpath, span_lint_and_sugg, SpanlessEq}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for implicit saturating subtraction. + /// + /// **Why is this bad?** Simplicity and readability. Instead we can easily use an builtin function. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let end: u32 = 10; + /// let start: u32 = 5; + /// + /// let mut i: u32 = end - start; + /// + /// // Bad + /// if i != 0 { + /// i -= 1; + /// } + /// ``` + /// Use instead: + /// ```rust + /// let end: u32 = 10; + /// let start: u32 = 5; + /// + /// let mut i: u32 = end - start; + /// + /// // Good + /// i = i.saturating_sub(1); + /// ``` + pub IMPLICIT_SATURATING_SUB, + pedantic, + "Perform saturating subtraction instead of implicitly checking lower bound of data type" +} + +declare_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitSaturatingSub { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) { + if in_macro(expr.span) { + return; + } + if_chain! { + if let Some((ref cond, ref then, None)) = higher::if_block(&expr); + + // Check if the conditional expression is a binary operation + if let ExprKind::Binary(ref cond_op, ref cond_left, ref cond_right) = cond.kind; + + // Ensure that the binary operator is >, != and < + if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node; + + // Check if the true condition block has only one statement + if let ExprKind::Block(ref block, _) = then.kind; + if block.stmts.len() == 1 && block.expr.is_none(); + + // Check if assign operation is done + if let StmtKind::Semi(ref e) = block.stmts[0].kind; + if let Some(target) = subtracts_one(cx, e); + + // Extracting out the variable name + if let ExprKind::Path(ref assign_path) = target.kind; + if let QPath::Resolved(_, ref ares_path) = assign_path; + + then { + // Handle symmetric conditions in the if statement + let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) { + if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node { + (cond_left, cond_right) + } else { + return; + } + } else if SpanlessEq::new(cx).eq_expr(cond_right, target) { + if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node { + (cond_right, cond_left) + } else { + return; + } + } else { + return; + }; + + // Check if the variable in the condition statement is an integer + if !cx.tables.expr_ty(cond_var).is_integral() { + return; + } + + // Get the variable name + let var_name = ares_path.segments[0].ident.name.as_str(); + const INT_TYPES: [&str; 5] = ["i8", "i16", "i32", "i64", "i128"]; + + match cond_num_val.kind { + ExprKind::Lit(ref cond_lit) => { + // Check if the constant is zero + if let LitKind::Int(0, _) = cond_lit.node { + if cx.tables.expr_ty(cond_left).is_signed() { + } else { + print_lint_and_sugg(cx, &var_name, expr); + }; + } + }, + ExprKind::Path(ref cond_num_path) => { + if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "MIN"])) { + print_lint_and_sugg(cx, &var_name, expr); + }; + }, + ExprKind::Call(ref func, _) => { + if let ExprKind::Path(ref cond_num_path) = func.kind { + if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "min_value"])) { + print_lint_and_sugg(cx, &var_name, expr); + } + }; + }, + _ => (), + } + } + } + } +} + +fn subtracts_one<'a>(cx: &LateContext<'_, '_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> { + match expr.kind { + ExprKind::AssignOp(ref op1, ref target, ref value) => { + if_chain! { + if BinOpKind::Sub == op1.node; + // Check if literal being subtracted is one + if let ExprKind::Lit(ref lit1) = value.kind; + if let LitKind::Int(1, _) = lit1.node; + then { + Some(target) + } else { + None + } + } + }, + ExprKind::Assign(ref target, ref value, _) => { + if_chain! { + if let ExprKind::Binary(ref op1, ref left1, ref right1) = value.kind; + if BinOpKind::Sub == op1.node; + + if SpanlessEq::new(cx).eq_expr(left1, target); + + if let ExprKind::Lit(ref lit1) = right1.kind; + if let LitKind::Int(1, _) = lit1.node; + then { + Some(target) + } else { + None + } + } + }, + _ => None, + } +} + +fn print_lint_and_sugg(cx: &LateContext<'_, '_>, var_name: &str, expr: &Expr<'_>) { + span_lint_and_sugg( + cx, + IMPLICIT_SATURATING_SUB, + expr.span, + "Implicitly performing saturating subtraction", + "try", + format!("{} = {}.saturating_sub({});", var_name, var_name, 1.to_string()), + Applicability::MachineApplicable, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/indexing_slicing.rs b/src/tools/clippy/clippy_lints/src/indexing_slicing.rs new file mode 100644 index 0000000000000..c5808dd540b6f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/indexing_slicing.rs @@ -0,0 +1,193 @@ +//! lint on indexing and slicing operations + +use crate::consts::{constant, Constant}; +use crate::utils::{higher, span_lint, span_lint_and_help}; +use rustc_ast::ast::RangeLimits; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for out of bounds array indexing with a constant + /// index. + /// + /// **Why is this bad?** This will always panic at runtime. + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// ```no_run + /// # #![allow(const_err)] + /// let x = [1, 2, 3, 4]; + /// + /// // Bad + /// x[9]; + /// &x[2..9]; + /// + /// // Good + /// x[0]; + /// x[3]; + /// ``` + pub OUT_OF_BOUNDS_INDEXING, + correctness, + "out of bounds constant indexing" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of indexing or slicing. Arrays are special cases, this lint + /// does report on arrays if we can tell that slicing operations are in bounds and does not + /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint. + /// + /// **Why is this bad?** Indexing and slicing can panic at runtime and there are + /// safe alternatives. + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// ```rust,no_run + /// // Vector + /// let x = vec![0; 5]; + /// + /// // Bad + /// x[2]; + /// &x[2..100]; + /// &x[2..]; + /// &x[..100]; + /// + /// // Good + /// x.get(2); + /// x.get(2..100); + /// x.get(2..); + /// x.get(..100); + /// + /// // Array + /// let y = [0, 1, 2, 3]; + /// + /// // Bad + /// &y[10..100]; + /// &y[10..]; + /// &y[..100]; + /// + /// // Good + /// &y[2..]; + /// &y[..2]; + /// &y[0..3]; + /// y.get(10); + /// y.get(10..100); + /// y.get(10..); + /// y.get(..100); + /// ``` + pub INDEXING_SLICING, + restriction, + "indexing/slicing usage" +} + +declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IndexingSlicing { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Index(ref array, ref index) = &expr.kind { + let ty = cx.tables.expr_ty(array); + if let Some(range) = higher::range(cx, index) { + // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..] + if let ty::Array(_, s) = ty.kind { + let size: u128 = if let Some(size) = s.try_eval_usize(cx.tcx, cx.param_env) { + size.into() + } else { + return; + }; + + let const_range = to_const_range(cx, range, size); + + if let (Some(start), _) = const_range { + if start > size { + span_lint( + cx, + OUT_OF_BOUNDS_INDEXING, + range.start.map_or(expr.span, |start| start.span), + "range is out of bounds", + ); + return; + } + } + + if let (_, Some(end)) = const_range { + if end > size { + span_lint( + cx, + OUT_OF_BOUNDS_INDEXING, + range.end.map_or(expr.span, |end| end.span), + "range is out of bounds", + ); + return; + } + } + + if let (Some(_), Some(_)) = const_range { + // early return because both start and end are constants + // and we have proven above that they are in bounds + return; + } + } + + let help_msg = match (range.start, range.end) { + (None, Some(_)) => "Consider using `.get(..n)`or `.get_mut(..n)` instead", + (Some(_), None) => "Consider using `.get(n..)` or .get_mut(n..)` instead", + (Some(_), Some(_)) => "Consider using `.get(n..m)` or `.get_mut(n..m)` instead", + (None, None) => return, // [..] is ok. + }; + + span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic.", None, help_msg); + } else { + // Catchall non-range index, i.e., [n] or [n << m] + if let ty::Array(..) = ty.kind { + // Index is a constant uint. + if let Some(..) = constant(cx, cx.tables, index) { + // Let rustc's `const_err` lint handle constant `usize` indexing on arrays. + return; + } + } + + span_lint_and_help( + cx, + INDEXING_SLICING, + expr.span, + "indexing may panic.", + None, + "Consider using `.get(n)` or `.get_mut(n)` instead", + ); + } + } + } +} + +/// Returns a tuple of options with the start and end (exclusive) values of +/// the range. If the start or end is not constant, None is returned. +fn to_const_range<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + range: higher::Range<'_>, + array_size: u128, +) -> (Option, Option) { + let s = range.start.map(|expr| constant(cx, cx.tables, expr).map(|(c, _)| c)); + let start = match s { + Some(Some(Constant::Int(x))) => Some(x), + Some(_) => None, + None => Some(0), + }; + + let e = range.end.map(|expr| constant(cx, cx.tables, expr).map(|(c, _)| c)); + let end = match e { + Some(Some(Constant::Int(x))) => { + if range.limits == RangeLimits::Closed { + Some(x + 1) + } else { + Some(x) + } + }, + Some(_) => None, + None => Some(array_size), + }; + + (start, end) +} diff --git a/src/tools/clippy/clippy_lints/src/infinite_iter.rs b/src/tools/clippy/clippy_lints/src/infinite_iter.rs new file mode 100644 index 0000000000000..cd989c0ea6f67 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/infinite_iter.rs @@ -0,0 +1,253 @@ +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{get_trait_def_id, higher, implements_trait, match_qpath, match_type, paths, span_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for iteration that is guaranteed to be infinite. + /// + /// **Why is this bad?** While there may be places where this is acceptable + /// (e.g., in event streams), in most cases this is simply an error. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// use std::iter; + /// + /// iter::repeat(1_u8).collect::>(); + /// ``` + pub INFINITE_ITER, + correctness, + "infinite iteration" +} + +declare_clippy_lint! { + /// **What it does:** Checks for iteration that may be infinite. + /// + /// **Why is this bad?** While there may be places where this is acceptable + /// (e.g., in event streams), in most cases this is simply an error. + /// + /// **Known problems:** The code may have a condition to stop iteration, but + /// this lint is not clever enough to analyze it. + /// + /// **Example:** + /// ```rust + /// let infinite_iter = 0..; + /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); + /// ``` + pub MAYBE_INFINITE_ITER, + pedantic, + "possible infinite iteration" +} + +declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InfiniteIter { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + let (lint, msg) = match complete_infinite_iter(cx, expr) { + Infinite => (INFINITE_ITER, "infinite iteration detected"), + MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"), + Finite => { + return; + }, + }; + span_lint(cx, lint, expr.span, msg) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum Finiteness { + Infinite, + MaybeInfinite, + Finite, +} + +use self::Finiteness::{Finite, Infinite, MaybeInfinite}; + +impl Finiteness { + #[must_use] + fn and(self, b: Self) -> Self { + match (self, b) { + (Finite, _) | (_, Finite) => Finite, + (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite, + _ => Infinite, + } + } + + #[must_use] + fn or(self, b: Self) -> Self { + match (self, b) { + (Infinite, _) | (_, Infinite) => Infinite, + (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite, + _ => Finite, + } + } +} + +impl From for Finiteness { + #[must_use] + fn from(b: bool) -> Self { + if b { + Infinite + } else { + Finite + } + } +} + +/// This tells us what to look for to know if the iterator returned by +/// this method is infinite +#[derive(Copy, Clone)] +enum Heuristic { + /// infinite no matter what + Always, + /// infinite if the first argument is + First, + /// infinite if any of the supplied arguments is + Any, + /// infinite if all of the supplied arguments are + All, +} + +use self::Heuristic::{All, Always, Any, First}; + +/// a slice of (method name, number of args, heuristic, bounds) tuples +/// that will be used to determine whether the method in question +/// returns an infinite or possibly infinite iterator. The finiteness +/// is an upper bound, e.g., some methods can return a possibly +/// infinite iterator at worst, e.g., `take_while`. +const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [ + ("zip", 2, All, Infinite), + ("chain", 2, Any, Infinite), + ("cycle", 1, Always, Infinite), + ("map", 2, First, Infinite), + ("by_ref", 1, First, Infinite), + ("cloned", 1, First, Infinite), + ("rev", 1, First, Infinite), + ("inspect", 1, First, Infinite), + ("enumerate", 1, First, Infinite), + ("peekable", 2, First, Infinite), + ("fuse", 1, First, Infinite), + ("skip", 2, First, Infinite), + ("skip_while", 1, First, Infinite), + ("filter", 2, First, Infinite), + ("filter_map", 2, First, Infinite), + ("flat_map", 2, First, Infinite), + ("unzip", 1, First, Infinite), + ("take_while", 2, First, MaybeInfinite), + ("scan", 3, First, MaybeInfinite), +]; + +fn is_infinite(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Finiteness { + match expr.kind { + ExprKind::MethodCall(ref method, _, ref args) => { + for &(name, len, heuristic, cap) in &HEURISTICS { + if method.ident.name.as_str() == name && args.len() == len { + return (match heuristic { + Always => Infinite, + First => is_infinite(cx, &args[0]), + Any => is_infinite(cx, &args[0]).or(is_infinite(cx, &args[1])), + All => is_infinite(cx, &args[0]).and(is_infinite(cx, &args[1])), + }) + .and(cap); + } + } + if method.ident.name == sym!(flat_map) && args.len() == 2 { + if let ExprKind::Closure(_, _, body_id, _, _) = args[1].kind { + let body = cx.tcx.hir().body(body_id); + return is_infinite(cx, &body.value); + } + } + Finite + }, + ExprKind::Block(ref block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)), + ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) => is_infinite(cx, e), + ExprKind::Call(ref path, _) => { + if let ExprKind::Path(ref qpath) = path.kind { + match_qpath(qpath, &paths::REPEAT).into() + } else { + Finite + } + }, + ExprKind::Struct(..) => higher::range(cx, expr).map_or(false, |r| r.end.is_none()).into(), + _ => Finite, + } +} + +/// the names and argument lengths of methods that *may* exhaust their +/// iterators +const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [ + ("find", 2), + ("rfind", 2), + ("position", 2), + ("rposition", 2), + ("any", 2), + ("all", 2), +]; + +/// the names and argument lengths of methods that *always* exhaust +/// their iterators +const COMPLETING_METHODS: [(&str, usize); 12] = [ + ("count", 1), + ("fold", 3), + ("for_each", 2), + ("partition", 2), + ("max", 1), + ("max_by", 2), + ("max_by_key", 2), + ("min", 1), + ("min_by", 2), + ("min_by_key", 2), + ("sum", 1), + ("product", 1), +]; + +/// the paths of types that are known to be infinitely allocating +const INFINITE_COLLECTORS: [&[&str]; 8] = [ + &paths::BINARY_HEAP, + &paths::BTREEMAP, + &paths::BTREESET, + &paths::HASHMAP, + &paths::HASHSET, + &paths::LINKED_LIST, + &paths::VEC, + &paths::VEC_DEQUE, +]; + +fn complete_infinite_iter(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Finiteness { + match expr.kind { + ExprKind::MethodCall(ref method, _, ref args) => { + for &(name, len) in &COMPLETING_METHODS { + if method.ident.name.as_str() == name && args.len() == len { + return is_infinite(cx, &args[0]); + } + } + for &(name, len) in &POSSIBLY_COMPLETING_METHODS { + if method.ident.name.as_str() == name && args.len() == len { + return MaybeInfinite.and(is_infinite(cx, &args[0])); + } + } + if method.ident.name == sym!(last) && args.len() == 1 { + let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR) + .map_or(false, |id| !implements_trait(cx, cx.tables.expr_ty(&args[0]), id, &[])); + if not_double_ended { + return is_infinite(cx, &args[0]); + } + } else if method.ident.name == sym!(collect) { + let ty = cx.tables.expr_ty(expr); + if INFINITE_COLLECTORS.iter().any(|path| match_type(cx, ty, path)) { + return is_infinite(cx, &args[0]); + } + } + }, + ExprKind::Binary(op, ref l, ref r) => { + if op.node.is_comparison() { + return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite); + } + }, // TODO: ExprKind::Loop + Match + _ => (), + } + Finite +} diff --git a/src/tools/clippy/clippy_lints/src/inherent_impl.rs b/src/tools/clippy/clippy_lints/src/inherent_impl.rs new file mode 100644 index 0000000000000..7e2975ac2ae90 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inherent_impl.rs @@ -0,0 +1,94 @@ +//! lint on inherent implementations + +use crate::utils::{in_macro, span_lint_and_then}; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{def_id, Crate, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for multiple inherent implementations of a struct + /// + /// **Why is this bad?** Splitting the implementation of a type makes the code harder to navigate. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct X; + /// impl X { + /// fn one() {} + /// } + /// impl X { + /// fn other() {} + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// struct X; + /// impl X { + /// fn one() {} + /// fn other() {} + /// } + /// ``` + pub MULTIPLE_INHERENT_IMPL, + restriction, + "Multiple inherent impl that could be grouped" +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Default)] +pub struct MultipleInherentImpl { + impls: FxHashMap, +} + +impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MultipleInherentImpl { + fn check_item(&mut self, _: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl { + ref generics, + of_trait: None, + .. + } = item.kind + { + // Remember for each inherent implementation encoutered its span and generics + // but filter out implementations that have generic params (type or lifetime) + // or are derived from a macro + if !in_macro(item.span) && generics.params.is_empty() { + self.impls.insert(item.hir_id.owner.to_def_id(), item.span); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx Crate<'_>) { + if let Some(item) = krate.items.values().next() { + // Retrieve all inherent implementations from the crate, grouped by type + for impls in cx + .tcx + .crate_inherent_impls(item.hir_id.owner.to_def_id().krate) + .inherent_impls + .values() + { + // Filter out implementations that have generic params (type or lifetime) + let mut impl_spans = impls.iter().filter_map(|impl_def| self.impls.get(impl_def)); + if let Some(initial_span) = impl_spans.next() { + impl_spans.for_each(|additional_span| { + span_lint_and_then( + cx, + MULTIPLE_INHERENT_IMPL, + *additional_span, + "Multiple implementations of this structure", + |diag| { + diag.span_note(*initial_span, "First implementation here"); + }, + ) + }) + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs new file mode 100644 index 0000000000000..289628a2752af --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs @@ -0,0 +1,156 @@ +use if_chain::if_chain; +use rustc_hir::{ImplItem, ImplItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{ + get_trait_def_id, implements_trait, is_type_diagnostic_item, paths, return_ty, span_lint_and_help, + trait_ref_of_method, walk_ptrs_ty, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`. + /// + /// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred. + /// + /// **Known problems:** None + /// + /// ** Example:** + /// + /// ```rust + /// // Bad + /// pub struct A; + /// + /// impl A { + /// pub fn to_string(&self) -> String { + /// "I am A".to_string() + /// } + /// } + /// ``` + /// + /// ```rust + /// // Good + /// use std::fmt; + /// + /// pub struct A; + /// + /// impl fmt::Display for A { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "I am A") + /// } + /// } + /// ``` + pub INHERENT_TO_STRING, + style, + "type implements inherent method `to_string()`, but should instead implement the `Display` trait" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait. + /// + /// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`. + /// + /// **Known problems:** None + /// + /// ** Example:** + /// + /// ```rust + /// // Bad + /// use std::fmt; + /// + /// pub struct A; + /// + /// impl A { + /// pub fn to_string(&self) -> String { + /// "I am A".to_string() + /// } + /// } + /// + /// impl fmt::Display for A { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "I am A, too") + /// } + /// } + /// ``` + /// + /// ```rust + /// // Good + /// use std::fmt; + /// + /// pub struct A; + /// + /// impl fmt::Display for A { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "I am A") + /// } + /// } + /// ``` + pub INHERENT_TO_STRING_SHADOW_DISPLAY, + correctness, + "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait" +} + +declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InherentToString { + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem<'_>) { + if impl_item.span.from_expansion() { + return; + } + + if_chain! { + // Check if item is a method, called to_string and has a parameter 'self' + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; + if impl_item.ident.name.as_str() == "to_string"; + let decl = &signature.decl; + if decl.implicit_self.has_implicit_self(); + if decl.inputs.len() == 1; + + // Check if return type is String + if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)); + + // Filters instances of to_string which are required by a trait + if trait_ref_of_method(cx, impl_item.hir_id).is_none(); + + then { + show_lint(cx, impl_item); + } + } + } +} + +fn show_lint(cx: &LateContext<'_, '_>, item: &ImplItem<'_>) { + let display_trait_id = get_trait_def_id(cx, &paths::DISPLAY_TRAIT).expect("Failed to get trait ID of `Display`!"); + + // Get the real type of 'self' + let fn_def_id = cx.tcx.hir().local_def_id(item.hir_id); + let self_type = cx.tcx.fn_sig(fn_def_id).input(0); + let self_type = walk_ptrs_ty(self_type.skip_binder()); + + // Emit either a warning or an error + if implements_trait(cx, self_type, display_trait_id, &[]) { + span_lint_and_help( + cx, + INHERENT_TO_STRING_SHADOW_DISPLAY, + item.span, + &format!( + "type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`", + self_type.to_string() + ), + None, + &format!("remove the inherent method from type `{}`", self_type.to_string()) + ); + } else { + span_lint_and_help( + cx, + INHERENT_TO_STRING, + item.span, + &format!( + "implementation of inherent method `to_string(&self) -> String` for type `{}`", + self_type.to_string() + ), + None, + &format!("implement trait `Display` for type `{}` instead", self_type.to_string()), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs new file mode 100644 index 0000000000000..1ebfb3c8162a1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs @@ -0,0 +1,57 @@ +//! checks for `#[inline]` on trait methods without bodies + +use crate::utils::span_lint_and_then; +use crate::utils::sugg::DiagnosticBuilderExt; +use rustc_ast::ast::{Attribute, Name}; +use rustc_errors::Applicability; +use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `#[inline]` on trait methods without bodies + /// + /// **Why is this bad?** Only implementations of trait methods may be inlined. + /// The inline attribute is ignored for trait methods without bodies. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// trait Animal { + /// #[inline] + /// fn name(&self) -> &'static str; + /// } + /// ``` + pub INLINE_FN_WITHOUT_BODY, + correctness, + "use of `#[inline]` on trait methods without bodies" +} + +declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InlineFnWithoutBody { + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind { + check_attrs(cx, item.ident.name, &item.attrs); + } + } +} + +fn check_attrs(cx: &LateContext<'_, '_>, name: Name, attrs: &[Attribute]) { + for attr in attrs { + if !attr.check_name(sym!(inline)) { + continue; + } + + span_lint_and_then( + cx, + INLINE_FN_WITHOUT_BODY, + attr.span, + &format!("use of `#[inline]` on trait method `{}` which has no body", name), + |diag| { + diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable); + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/int_plus_one.rs b/src/tools/clippy/clippy_lints/src/int_plus_one.rs new file mode 100644 index 0000000000000..d5dbd495680b2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/int_plus_one.rs @@ -0,0 +1,172 @@ +//! lint on blocks unnecessarily using >= with a + 1 or - 1 + +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, Lit, LitKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{snippet_opt, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block + /// + /// + /// **Why is this bad?** Readability -- better to use `> y` instead of `>= y + 1`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// # let y = 1; + /// if x >= y + 1 {} + /// ``` + /// + /// Could be written as: + /// + /// ```rust + /// # let x = 1; + /// # let y = 1; + /// if x > y {} + /// ``` + pub INT_PLUS_ONE, + complexity, + "instead of using `x >= y + 1`, use `x > y`" +} + +declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]); + +// cases: +// BinOpKind::Ge +// x >= y + 1 +// x - 1 >= y +// +// BinOpKind::Le +// x + 1 <= y +// x <= y - 1 + +#[derive(Copy, Clone)] +enum Side { + LHS, + RHS, +} + +impl IntPlusOne { + #[allow(clippy::cast_sign_loss)] + fn check_lit(lit: &Lit, target_value: i128) -> bool { + if let LitKind::Int(value, ..) = lit.kind { + return value == (target_value as u128); + } + false + } + + fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option { + match (binop, &lhs.kind, &rhs.kind) { + // case where `x - 1 >= ...` or `-1 + x >= ...` + (BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => { + match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) { + // `-1 + x` + (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => { + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::LHS) + }, + // `x - 1` + (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::LHS) + }, + _ => None, + } + }, + // case where `... >= y + 1` or `... >= 1 + y` + (BinOpKind::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) + if rhskind.node == BinOpKind::Add => + { + match (&rhslhs.kind, &rhsrhs.kind) { + // `y + 1` and `1 + y` + (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::RHS) + }, + (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::RHS) + }, + _ => None, + } + } + // case where `x + 1 <= ...` or `1 + x <= ...` + (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) + if lhskind.node == BinOpKind::Add => + { + match (&lhslhs.kind, &lhsrhs.kind) { + // `1 + x` and `x + 1` + (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::LHS) + }, + (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::LHS) + }, + _ => None, + } + } + // case where `... >= y - 1` or `... >= -1 + y` + (BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => { + match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) { + // `-1 + y` + (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => { + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::RHS) + }, + // `y - 1` + (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::RHS) + }, + _ => None, + } + }, + _ => None, + } + } + + fn generate_recommendation( + cx: &EarlyContext<'_>, + binop: BinOpKind, + node: &Expr, + other_side: &Expr, + side: Side, + ) -> Option { + let binop_string = match binop { + BinOpKind::Ge => ">", + BinOpKind::Le => "<", + _ => return None, + }; + if let Some(snippet) = snippet_opt(cx, node.span) { + if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) { + let rec = match side { + Side::LHS => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)), + Side::RHS => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)), + }; + return rec; + } + } + None + } + + fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) { + span_lint_and_sugg( + cx, + INT_PLUS_ONE, + block.span, + "Unnecessary `>= y + 1` or `x - 1 >=`", + "change it to", + recommendation, + Applicability::MachineApplicable, // snippet + ); + } +} + +impl EarlyLintPass for IntPlusOne { + fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) { + if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind { + if let Some(ref rec) = Self::check_binop(cx, kind.node, lhs, rhs) { + Self::emit_warning(cx, item, rec.clone()); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/integer_division.rs b/src/tools/clippy/clippy_lints/src/integer_division.rs new file mode 100644 index 0000000000000..fe34d33fe652c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/integer_division.rs @@ -0,0 +1,56 @@ +use crate::utils::span_lint_and_help; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for division of integers + /// + /// **Why is this bad?** When outside of some very specific algorithms, + /// integer division is very often a mistake because it discards the + /// remainder. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn main() { + /// let x = 3 / 2; + /// println!("{}", x); + /// } + /// ``` + pub INTEGER_DIVISION, + restriction, + "integer division may cause loss of precision" +} + +declare_lint_pass!(IntegerDivision => [INTEGER_DIVISION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IntegerDivision { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if is_integer_division(cx, expr) { + span_lint_and_help( + cx, + INTEGER_DIVISION, + expr.span, + "integer division", + None, + "division of integers may cause loss of precision. consider using floats.", + ); + } + } +} + +fn is_integer_division<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) -> bool { + if_chain! { + if let hir::ExprKind::Binary(binop, left, right) = &expr.kind; + if let hir::BinOpKind::Div = &binop.node; + then { + let (left_ty, right_ty) = (cx.tables.expr_ty(left), cx.tables.expr_ty(right)); + return left_ty.is_integral() && right_ty.is_integral(); + } + } + + false +} diff --git a/src/tools/clippy/clippy_lints/src/items_after_statements.rs b/src/tools/clippy/clippy_lints/src/items_after_statements.rs new file mode 100644 index 0000000000000..e7062b7c16bb9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/items_after_statements.rs @@ -0,0 +1,71 @@ +//! lint when items are used after statements + +use crate::utils::span_lint; +use rustc_ast::ast::{Block, ItemKind, StmtKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for items declared after some statement in a block. + /// + /// **Why is this bad?** Items live for the entire scope they are declared + /// in. But statements are processed in order. This might cause confusion as + /// it's hard to figure out which item is meant in a statement. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo() { + /// println!("cake"); + /// } + /// + /// fn main() { + /// foo(); // prints "foo" + /// fn foo() { + /// println!("foo"); + /// } + /// foo(); // prints "foo" + /// } + /// ``` + pub ITEMS_AFTER_STATEMENTS, + pedantic, + "blocks where an item comes after a statement" +} + +declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]); + +impl EarlyLintPass for ItemsAfterStatements { + fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) { + if item.span.from_expansion() { + return; + } + + // skip initial items + let stmts = item + .stmts + .iter() + .map(|stmt| &stmt.kind) + .skip_while(|s| matches!(**s, StmtKind::Item(..))); + + // lint on all further items + for stmt in stmts { + if let StmtKind::Item(ref it) = *stmt { + if it.span.from_expansion() { + return; + } + if let ItemKind::MacroDef(..) = it.kind { + // do not lint `macro_rules`, but continue processing further statements + continue; + } + span_lint( + cx, + ITEMS_AFTER_STATEMENTS, + it.span, + "adding items after statements is confusing, since items exist from the \ + start of the scope", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs new file mode 100644 index 0000000000000..c9e12fc535ec0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs @@ -0,0 +1,85 @@ +use crate::rustc_target::abi::LayoutOf; +use crate::utils::span_lint_and_then; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::interpret::ConstValue; +use rustc_middle::ty::{self, ConstKind}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{BytePos, Pos, Span}; +use rustc_typeck::hir_ty_to_ty; + +declare_clippy_lint! { + /// **What it does:** Checks for large `const` arrays that should + /// be defined as `static` instead. + /// + /// **Why is this bad?** Performance: const variables are inlined upon use. + /// Static items result in only one instance and has a fixed location in memory. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// // Bad + /// pub const a = [0u32; 1_000_000]; + /// + /// // Good + /// pub static a = [0u32; 1_000_000]; + /// ``` + pub LARGE_CONST_ARRAYS, + perf, + "large non-scalar const array may cause performance overhead" +} + +pub struct LargeConstArrays { + maximum_allowed_size: u64, +} + +impl LargeConstArrays { + #[must_use] + pub fn new(maximum_allowed_size: u64) -> Self { + Self { maximum_allowed_size } + } +} + +impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeConstArrays { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if_chain! { + if !item.span.from_expansion(); + if let ItemKind::Const(hir_ty, _) = &item.kind; + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + if let ty::Array(element_type, cst) = ty.kind; + if let ConstKind::Value(val) = cst.val; + if let ConstValue::Scalar(element_count) = val; + if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); + if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if self.maximum_allowed_size < element_count * element_size; + + then { + let hi_pos = item.ident.span.lo() - BytePos::from_usize(1); + let sugg_span = Span::new( + hi_pos - BytePos::from_usize("const".len()), + hi_pos, + item.span.ctxt(), + ); + span_lint_and_then( + cx, + LARGE_CONST_ARRAYS, + item.span, + "large array defined as const", + |diag| { + diag.span_suggestion( + sugg_span, + "make this a static item", + "static".to_string(), + Applicability::MachineApplicable, + ); + } + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/large_enum_variant.rs b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs new file mode 100644 index 0000000000000..5bc3234e3252f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs @@ -0,0 +1,134 @@ +//! lint when there is a large size difference between variants on an enum + +use crate::utils::{snippet_opt, span_lint_and_then}; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind, VariantData}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_target::abi::LayoutOf; + +declare_clippy_lint! { + /// **What it does:** Checks for large size differences between variants on + /// `enum`s. + /// + /// **Why is this bad?** Enum size is bounded by the largest variant. Having a + /// large variant can penalize the memory layout of that enum. + /// + /// **Known problems:** This lint obviously cannot take the distribution of + /// variants in your running program into account. It is possible that the + /// smaller variants make up less than 1% of all instances, in which case + /// the overhead is negligible and the boxing is counter-productive. Always + /// measure the change this lint suggests. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// enum Test { + /// A(i32), + /// B([i32; 8000]), + /// } + /// + /// // Possibly better + /// enum Test2 { + /// A(i32), + /// B(Box<[i32; 8000]>), + /// } + /// ``` + pub LARGE_ENUM_VARIANT, + perf, + "large size difference between variants on an enum" +} + +#[derive(Copy, Clone)] +pub struct LargeEnumVariant { + maximum_size_difference_allowed: u64, +} + +impl LargeEnumVariant { + #[must_use] + pub fn new(maximum_size_difference_allowed: u64) -> Self { + Self { + maximum_size_difference_allowed, + } + } +} + +impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeEnumVariant { + fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item<'_>) { + let did = cx.tcx.hir().local_def_id(item.hir_id); + if let ItemKind::Enum(ref def, _) = item.kind { + let ty = cx.tcx.type_of(did); + let adt = ty.ty_adt_def().expect("already checked whether this is an enum"); + + let mut largest_variant: Option<(_, _)> = None; + let mut second_variant: Option<(_, _)> = None; + + for (i, variant) in adt.variants.iter().enumerate() { + let size: u64 = variant + .fields + .iter() + .filter_map(|f| { + let ty = cx.tcx.type_of(f.did); + // don't count generics by filtering out everything + // that does not have a layout + cx.layout_of(ty).ok().map(|l| l.size.bytes()) + }) + .sum(); + + let grouped = (size, (i, variant)); + + if grouped.0 >= largest_variant.map_or(0, |x| x.0) { + second_variant = largest_variant; + largest_variant = Some(grouped); + } + } + + if let (Some(largest), Some(second)) = (largest_variant, second_variant) { + let difference = largest.0 - second.0; + + if difference > self.maximum_size_difference_allowed { + let (i, variant) = largest.1; + + let help_text = "consider boxing the large fields to reduce the total size of the enum"; + span_lint_and_then( + cx, + LARGE_ENUM_VARIANT, + def.variants[i].span, + "large size difference between variants", + |diag| { + diag.span_label( + def.variants[(largest.1).0].span, + &format!("this variant is {} bytes", largest.0), + ); + diag.span_note( + def.variants[(second.1).0].span, + &format!("and the second-largest variant is {} bytes:", second.0), + ); + if variant.fields.len() == 1 { + let span = match def.variants[i].data { + VariantData::Struct(ref fields, ..) | VariantData::Tuple(ref fields, ..) => { + fields[0].ty.span + }, + VariantData::Unit(..) => unreachable!(), + }; + if let Some(snip) = snippet_opt(cx, span) { + diag.span_suggestion( + span, + help_text, + format!("Box<{}>", snip), + Applicability::MaybeIncorrect, + ); + return; + } + } + diag.span_help(def.variants[i].span, help_text); + }, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs new file mode 100644 index 0000000000000..deb57db167896 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs @@ -0,0 +1,69 @@ +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::interpret::ConstValue; +use rustc_middle::ty::{self, ConstKind}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use if_chain::if_chain; + +use crate::rustc_target::abi::LayoutOf; +use crate::utils::{snippet, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** Checks for local arrays that may be too large. + /// + /// **Why is this bad?** Large local arrays may cause stack overflow. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let a = [0u32; 1_000_000]; + /// ``` + pub LARGE_STACK_ARRAYS, + pedantic, + "allocating large arrays on stack may cause stack overflow" +} + +pub struct LargeStackArrays { + maximum_allowed_size: u64, +} + +impl LargeStackArrays { + #[must_use] + pub fn new(maximum_allowed_size: u64) -> Self { + Self { maximum_allowed_size } + } +} + +impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeStackArrays { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Repeat(_, _) = expr.kind; + if let ty::Array(element_type, cst) = cx.tables.expr_ty(expr).kind; + if let ConstKind::Value(val) = cst.val; + if let ConstValue::Scalar(element_count) = val; + if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); + if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if self.maximum_allowed_size < element_count * element_size; + then { + span_lint_and_help( + cx, + LARGE_STACK_ARRAYS, + expr.span, + &format!( + "allocating a local array larger than {} bytes", + self.maximum_allowed_size + ), + None, + &format!( + "consider allocating on the heap with `vec!{}.into_boxed_slice()`", + snippet(cx, expr.span, "[...]") + ), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs new file mode 100644 index 0000000000000..1d86ca9696f2b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -0,0 +1,304 @@ +use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty}; +use rustc_ast::ast::{LitKind, Name}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::{AssocItemKind, BinOpKind, Expr, ExprKind, ImplItemRef, Item, ItemKind, TraitItemRef}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{Span, Spanned}; + +declare_clippy_lint! { + /// **What it does:** Checks for getting the length of something via `.len()` + /// just to compare to zero, and suggests using `.is_empty()` where applicable. + /// + /// **Why is this bad?** Some structures can answer `.is_empty()` much faster + /// than calculating their length. So it is good to get into the habit of using + /// `.is_empty()`, and having it is cheap. + /// Besides, it makes the intent clearer than a manual comparison in some contexts. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// if x.len() == 0 { + /// .. + /// } + /// if y.len() != 0 { + /// .. + /// } + /// ``` + /// instead use + /// ```ignore + /// if x.is_empty() { + /// .. + /// } + /// if !y.is_empty() { + /// .. + /// } + /// ``` + pub LEN_ZERO, + style, + "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead" +} + +declare_clippy_lint! { + /// **What it does:** Checks for items that implement `.len()` but not + /// `.is_empty()`. + /// + /// **Why is this bad?** It is good custom to have both methods, because for + /// some data structures, asking about the length will be a costly operation, + /// whereas `.is_empty()` can usually answer in constant time. Also it used to + /// lead to false positives on the [`len_zero`](#len_zero) lint – currently that + /// lint will ignore such entities. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// impl X { + /// pub fn len(&self) -> usize { + /// .. + /// } + /// } + /// ``` + pub LEN_WITHOUT_IS_EMPTY, + style, + "traits or impls with a public `len` method but no corresponding `is_empty` method" +} + +declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LenZero { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if item.span.from_expansion() { + return; + } + + match item.kind { + ItemKind::Trait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items), + ItemKind::Impl { + of_trait: None, + items: ref impl_items, + .. + } => check_impl_items(cx, item, impl_items), + _ => (), + } + } + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node: cmp, .. }, ref left, ref right) = expr.kind { + match cmp { + BinOpKind::Eq => { + check_cmp(cx, expr.span, left, right, "", 0); // len == 0 + check_cmp(cx, expr.span, right, left, "", 0); // 0 == len + }, + BinOpKind::Ne => { + check_cmp(cx, expr.span, left, right, "!", 0); // len != 0 + check_cmp(cx, expr.span, right, left, "!", 0); // 0 != len + }, + BinOpKind::Gt => { + check_cmp(cx, expr.span, left, right, "!", 0); // len > 0 + check_cmp(cx, expr.span, right, left, "", 1); // 1 > len + }, + BinOpKind::Lt => { + check_cmp(cx, expr.span, left, right, "", 1); // len < 1 + check_cmp(cx, expr.span, right, left, "!", 0); // 0 < len + }, + BinOpKind::Ge => check_cmp(cx, expr.span, left, right, "!", 1), // len >= 1 + BinOpKind::Le => check_cmp(cx, expr.span, right, left, "!", 1), // 1 <= len + _ => (), + } + } + } +} + +fn check_trait_items(cx: &LateContext<'_, '_>, visited_trait: &Item<'_>, trait_items: &[TraitItemRef]) { + fn is_named_self(cx: &LateContext<'_, '_>, item: &TraitItemRef, name: &str) -> bool { + item.ident.name.as_str() == name + && if let AssocItemKind::Fn { has_self } = item.kind { + has_self && { + let did = cx.tcx.hir().local_def_id(item.id.hir_id); + cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1 + } + } else { + false + } + } + + // fill the set with current and super traits + fn fill_trait_set(traitt: DefId, set: &mut FxHashSet, cx: &LateContext<'_, '_>) { + if set.insert(traitt) { + for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) { + fill_trait_set(supertrait, set, cx); + } + } + } + + if cx.access_levels.is_exported(visited_trait.hir_id) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) { + let mut current_and_super_traits = FxHashSet::default(); + let visited_trait_def_id = cx.tcx.hir().local_def_id(visited_trait.hir_id); + fill_trait_set(visited_trait_def_id.to_def_id(), &mut current_and_super_traits, cx); + + let is_empty_method_found = current_and_super_traits + .iter() + .flat_map(|&i| cx.tcx.associated_items(i).in_definition_order()) + .any(|i| { + i.kind == ty::AssocKind::Fn + && i.fn_has_self_parameter + && i.ident.name == sym!(is_empty) + && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1 + }); + + if !is_empty_method_found { + span_lint( + cx, + LEN_WITHOUT_IS_EMPTY, + visited_trait.span, + &format!( + "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method", + visited_trait.ident.name + ), + ); + } + } +} + +fn check_impl_items(cx: &LateContext<'_, '_>, item: &Item<'_>, impl_items: &[ImplItemRef<'_>]) { + fn is_named_self(cx: &LateContext<'_, '_>, item: &ImplItemRef<'_>, name: &str) -> bool { + item.ident.name.as_str() == name + && if let AssocItemKind::Fn { has_self } = item.kind { + has_self && { + let did = cx.tcx.hir().local_def_id(item.id.hir_id); + cx.tcx.fn_sig(did).inputs().skip_binder().len() == 1 + } + } else { + false + } + } + + let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) { + if cx.access_levels.is_exported(is_empty.id.hir_id) { + return; + } else { + "a private" + } + } else { + "no corresponding" + }; + + if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) { + if cx.access_levels.is_exported(i.id.hir_id) { + let def_id = cx.tcx.hir().local_def_id(item.hir_id); + let ty = cx.tcx.type_of(def_id); + + span_lint( + cx, + LEN_WITHOUT_IS_EMPTY, + item.span, + &format!( + "item `{}` has a public `len` method but {} `is_empty` method", + ty, is_empty + ), + ); + } + } +} + +fn check_cmp(cx: &LateContext<'_, '_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { + if let (&ExprKind::MethodCall(ref method_path, _, ref args), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind) { + // check if we are in an is_empty() method + if let Some(name) = get_item_name(cx, method) { + if name.as_str() == "is_empty" { + return; + } + } + + check_len(cx, span, method_path.ident.name, args, &lit.node, op, compare_to) + } +} + +fn check_len( + cx: &LateContext<'_, '_>, + span: Span, + method_name: Name, + args: &[Expr<'_>], + lit: &LitKind, + op: &str, + compare_to: u32, +) { + if let LitKind::Int(lit, _) = *lit { + // check if length is compared to the specified number + if lit != u128::from(compare_to) { + return; + } + + if method_name.as_str() == "len" && args.len() == 1 && has_is_empty(cx, &args[0]) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + LEN_ZERO, + span, + &format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }), + &format!("using `{}is_empty` is clearer and more explicit", op), + format!( + "{}{}.is_empty()", + op, + snippet_with_applicability(cx, args[0].span, "_", &mut applicability) + ), + applicability, + ); + } + } +} + +/// Checks if this type has an `is_empty` method. +fn has_is_empty(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. + fn is_is_empty(cx: &LateContext<'_, '_>, item: &ty::AssocItem) -> bool { + if let ty::AssocKind::Fn = item.kind { + if item.ident.name.as_str() == "is_empty" { + let sig = cx.tcx.fn_sig(item.def_id); + let ty = sig.skip_binder(); + ty.inputs().len() == 1 + } else { + false + } + } else { + false + } + } + + /// Checks the inherent impl's items for an `is_empty(self)` method. + fn has_is_empty_impl(cx: &LateContext<'_, '_>, id: DefId) -> bool { + cx.tcx.inherent_impls(id).iter().any(|imp| { + cx.tcx + .associated_items(*imp) + .in_definition_order() + .any(|item| is_is_empty(cx, &item)) + }) + } + + let ty = &walk_ptrs_ty(cx.tables.expr_ty(expr)); + match ty.kind { + ty::Dynamic(ref tt, ..) => { + if let Some(principal) = tt.principal() { + cx.tcx + .associated_items(principal.def_id()) + .in_definition_order() + .any(|item| is_is_empty(cx, &item)) + } else { + false + } + }, + ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id), + ty::Adt(id, _) => has_is_empty_impl(cx, id.did), + ty::Array(..) | ty::Slice(..) | ty::Str => true, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/let_if_seq.rs b/src/tools/clippy/clippy_lints/src/let_if_seq.rs new file mode 100644 index 0000000000000..398a3103a0371 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/let_if_seq.rs @@ -0,0 +1,205 @@ +use crate::utils::{higher, qpath_res, snippet, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::intravisit; +use rustc_hir::BindingAnnotation; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for variable declarations immediately followed by a + /// conditional affectation. + /// + /// **Why is this bad?** This is not idiomatic Rust. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let foo; + /// + /// if bar() { + /// foo = 42; + /// } else { + /// foo = 0; + /// } + /// + /// let mut baz = None; + /// + /// if bar() { + /// baz = Some(42); + /// } + /// ``` + /// + /// should be written + /// + /// ```rust,ignore + /// let foo = if bar() { + /// 42 + /// } else { + /// 0 + /// }; + /// + /// let baz = if bar() { + /// Some(42) + /// } else { + /// None + /// }; + /// ``` + pub USELESS_LET_IF_SEQ, + style, + "unidiomatic `let mut` declaration followed by initialization in `if`" +} + +declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetIfSeq { + fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx hir::Block<'_>) { + let mut it = block.stmts.iter().peekable(); + while let Some(stmt) = it.next() { + if_chain! { + if let Some(expr) = it.peek(); + if let hir::StmtKind::Local(ref local) = stmt.kind; + if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; + if let hir::StmtKind::Expr(ref if_) = expr.kind; + if let Some((ref cond, ref then, ref else_)) = higher::if_block(&if_); + if !used_in_expr(cx, canonical_id, cond); + if let hir::ExprKind::Block(ref then, _) = then.kind; + if let Some(value) = check_assign(cx, canonical_id, &*then); + if !used_in_expr(cx, canonical_id, value); + then { + let span = stmt.span.to(if_.span); + + let has_interior_mutability = !cx.tables.node_type(canonical_id).is_freeze( + cx.tcx, + cx.param_env, + span + ); + if has_interior_mutability { return; } + + let (default_multi_stmts, default) = if let Some(ref else_) = *else_ { + if let hir::ExprKind::Block(ref else_, _) = else_.kind { + if let Some(default) = check_assign(cx, canonical_id, else_) { + (else_.stmts.len() > 1, default) + } else if let Some(ref default) = local.init { + (true, &**default) + } else { + continue; + } + } else { + continue; + } + } else if let Some(ref default) = local.init { + (false, &**default) + } else { + continue; + }; + + let mutability = match mode { + BindingAnnotation::RefMut | BindingAnnotation::Mutable => " ", + _ => "", + }; + + // FIXME: this should not suggest `mut` if we can detect that the variable is not + // use mutably after the `if` + + let sug = format!( + "let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};", + mut=mutability, + name=ident.name, + cond=snippet(cx, cond.span, "_"), + then=if then.stmts.len() > 1 { " ..;" } else { "" }, + else=if default_multi_stmts { " ..;" } else { "" }, + value=snippet(cx, value.span, ""), + default=snippet(cx, default.span, ""), + ); + span_lint_and_then(cx, + USELESS_LET_IF_SEQ, + span, + "`if _ { .. } else { .. }` is an expression", + |diag| { + diag.span_suggestion( + span, + "it is more idiomatic to write", + sug, + Applicability::HasPlaceholders, + ); + if !mutability.is_empty() { + diag.note("you might not need `mut` at all"); + } + }); + } + } + } + } +} + +struct UsedVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + id: hir::HirId, + used: bool, +} + +impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if_chain! { + if let hir::ExprKind::Path(ref qpath) = expr.kind; + if let Res::Local(local_id) = qpath_res(self.cx, qpath, expr.hir_id); + if self.id == local_id; + then { + self.used = true; + return; + } + } + intravisit::walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} + +fn check_assign<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + decl: hir::HirId, + block: &'tcx hir::Block<'_>, +) -> Option<&'tcx hir::Expr<'tcx>> { + if_chain! { + if block.expr.is_none(); + if let Some(expr) = block.stmts.iter().last(); + if let hir::StmtKind::Semi(ref expr) = expr.kind; + if let hir::ExprKind::Assign(ref var, ref value, _) = expr.kind; + if let hir::ExprKind::Path(ref qpath) = var.kind; + if let Res::Local(local_id) = qpath_res(cx, qpath, var.hir_id); + if decl == local_id; + then { + let mut v = UsedVisitor { + cx, + id: decl, + used: false, + }; + + for s in block.stmts.iter().take(block.stmts.len()-1) { + intravisit::walk_stmt(&mut v, s); + + if v.used { + return None; + } + } + + return Some(value); + } + } + + None +} + +fn used_in_expr<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> bool { + let mut v = UsedVisitor { cx, id, used: false }; + intravisit::walk_expr(&mut v, expr); + v.used +} diff --git a/src/tools/clippy/clippy_lints/src/let_underscore.rs b/src/tools/clippy/clippy_lints/src/let_underscore.rs new file mode 100644 index 0000000000000..710dec8d33fc9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/let_underscore.rs @@ -0,0 +1,119 @@ +use if_chain::if_chain; +use rustc_hir::{Local, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{is_must_use_func_call, is_must_use_ty, match_type, paths, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** Checks for `let _ = ` + /// where expr is #[must_use] + /// + /// **Why is this bad?** It's better to explicitly + /// handle the value of a #[must_use] expr + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn f() -> Result { + /// Ok(0) + /// } + /// + /// let _ = f(); + /// // is_ok() is marked #[must_use] + /// let _ = f().is_ok(); + /// ``` + pub LET_UNDERSCORE_MUST_USE, + restriction, + "non-binding let on a `#[must_use]` expression" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `let _ = sync_lock` + /// + /// **Why is this bad?** This statement immediately drops the lock instead of + /// extending it's lifetime to the end of the scope, which is often not intended. + /// To extend lock lifetime to the end of the scope, use an underscore-prefixed + /// name instead (i.e. _lock). If you want to explicitly drop the lock, + /// `std::mem::drop` conveys your intention better and is less error-prone. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust,ignore + /// let _ = mutex.lock(); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// let _lock = mutex.lock(); + /// ``` + pub LET_UNDERSCORE_LOCK, + correctness, + "non-binding let on a synchronization lock" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK]); + +const SYNC_GUARD_PATHS: [&[&str]; 3] = [ + &paths::MUTEX_GUARD, + &paths::RWLOCK_READ_GUARD, + &paths::RWLOCK_WRITE_GUARD, +]; + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetUnderscore { + fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &Local<'_>) { + if in_external_macro(cx.tcx.sess, local.span) { + return; + } + + if_chain! { + if let PatKind::Wild = local.pat.kind; + if let Some(ref init) = local.init; + then { + let init_ty = cx.tables.expr_ty(init); + let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { + GenericArgKind::Type(inner_ty) => { + SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)) + }, + + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, + }); + if contains_sync_guard { + span_lint_and_help( + cx, + LET_UNDERSCORE_LOCK, + local.span, + "non-binding let on a synchronization lock", + None, + "consider using an underscore-prefixed named \ + binding or dropping explicitly with `std::mem::drop`" + ) + } else if is_must_use_ty(cx, cx.tables.expr_ty(init)) { + span_lint_and_help( + cx, + LET_UNDERSCORE_MUST_USE, + local.span, + "non-binding let on an expression with `#[must_use]` type", + None, + "consider explicitly using expression value" + ) + } else if is_must_use_func_call(cx, init) { + span_lint_and_help( + cx, + LET_UNDERSCORE_MUST_USE, + local.span, + "non-binding let on a result of a `#[must_use]` function", + None, + "consider explicitly using function result" + ) + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs new file mode 100644 index 0000000000000..c995be5edc25c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -0,0 +1,1795 @@ +// error-pattern:cargo-clippy + +#![feature(box_syntax)] +#![feature(box_patterns)] +#![feature(or_patterns)] +#![feature(rustc_private)] +#![feature(stmt_expr_attributes)] +#![allow(clippy::missing_docs_in_private_items, clippy::must_use_candidate)] +#![recursion_limit = "512"] +#![warn(rust_2018_idioms, trivial_casts, trivial_numeric_casts)] +#![deny(rustc::internal)] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![feature(crate_visibility_modifier)] +#![feature(concat_idents)] + +// FIXME: switch to something more ergonomic here, once available. +// (Currently there is no way to opt into sysroot crates without `extern crate`.) +#[allow(unused_extern_crates)] +extern crate fmt_macros; +#[allow(unused_extern_crates)] +extern crate rustc_ast; +#[allow(unused_extern_crates)] +extern crate rustc_ast_pretty; +#[allow(unused_extern_crates)] +extern crate rustc_attr; +#[allow(unused_extern_crates)] +extern crate rustc_data_structures; +#[allow(unused_extern_crates)] +extern crate rustc_driver; +#[allow(unused_extern_crates)] +extern crate rustc_errors; +#[allow(unused_extern_crates)] +extern crate rustc_hir; +#[allow(unused_extern_crates)] +extern crate rustc_hir_pretty; +#[allow(unused_extern_crates)] +extern crate rustc_index; +#[allow(unused_extern_crates)] +extern crate rustc_infer; +#[allow(unused_extern_crates)] +extern crate rustc_lexer; +#[allow(unused_extern_crates)] +extern crate rustc_lint; +#[allow(unused_extern_crates)] +extern crate rustc_middle; +#[allow(unused_extern_crates)] +extern crate rustc_mir; +#[allow(unused_extern_crates)] +extern crate rustc_parse; +#[allow(unused_extern_crates)] +extern crate rustc_session; +#[allow(unused_extern_crates)] +extern crate rustc_span; +#[allow(unused_extern_crates)] +extern crate rustc_target; +#[allow(unused_extern_crates)] +extern crate rustc_trait_selection; +#[allow(unused_extern_crates)] +extern crate rustc_typeck; + +use rustc_data_structures::fx::FxHashSet; +use rustc_lint::LintId; +use rustc_session::Session; + +/// Macro used to declare a Clippy lint. +/// +/// Every lint declaration consists of 4 parts: +/// +/// 1. The documentation, which is used for the website +/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions. +/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or +/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of. +/// 4. The `description` that contains a short explanation on what's wrong with code where the +/// lint is triggered. +/// +/// Currently the categories `style`, `correctness`, `complexity` and `perf` are enabled by default. +/// As said in the README.md of this repository, if the lint level mapping changes, please update +/// README.md. +/// +/// # Example +/// +/// ``` +/// # #![feature(rustc_private)] +/// # #[allow(unused_extern_crates)] +/// # extern crate rustc_middle; +/// # #[allow(unused_extern_crates)] +/// # extern crate rustc_session; +/// # #[macro_use] +/// # use clippy_lints::declare_clippy_lint; +/// use rustc_session::declare_tool_lint; +/// +/// declare_clippy_lint! { +/// /// **What it does:** Checks for ... (describe what the lint matches). +/// /// +/// /// **Why is this bad?** Supply the reason for linting the code. +/// /// +/// /// **Known problems:** None. (Or describe where it could go wrong.) +/// /// +/// /// **Example:** +/// /// +/// /// ```rust +/// /// // Bad +/// /// Insert a short example of code that triggers the lint +/// /// +/// /// // Good +/// /// Insert a short example of improved code that doesn't trigger the lint +/// /// ``` +/// pub LINT_NAME, +/// pedantic, +/// "description" +/// } +/// ``` +/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints +#[macro_export] +macro_rules! declare_clippy_lint { + { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; +} + +mod consts; +#[macro_use] +mod utils; + +// begin lints modules, do not remove this comment, it’s used in `update_lints` +mod approx_const; +mod arithmetic; +mod as_conversions; +mod assertions_on_constants; +mod assign_ops; +mod atomic_ordering; +mod attrs; +mod await_holding_lock; +mod bit_mask; +mod blacklisted_name; +mod block_in_if_condition; +mod booleans; +mod bytecount; +mod cargo_common_metadata; +mod checked_conversions; +mod cognitive_complexity; +mod collapsible_if; +mod comparison_chain; +mod copies; +mod copy_iterator; +mod dbg_macro; +mod default_trait_access; +mod dereference; +mod derive; +mod doc; +mod double_comparison; +mod double_parens; +mod drop_bounds; +mod drop_forget_ref; +mod duration_subsec; +mod else_if_without_else; +mod empty_enum; +mod entry; +mod enum_clike; +mod enum_variants; +mod eq_op; +mod erasing_op; +mod escape; +mod eta_reduction; +mod eval_order_dependence; +mod excessive_bools; +mod exit; +mod explicit_write; +mod fallible_impl_from; +mod float_literal; +mod floating_point_arithmetic; +mod format; +mod formatting; +mod functions; +mod future_not_send; +mod get_last_with_len; +mod identity_conversion; +mod identity_op; +mod if_let_mutex; +mod if_let_some_result; +mod if_not_else; +mod implicit_return; +mod implicit_saturating_sub; +mod indexing_slicing; +mod infinite_iter; +mod inherent_impl; +mod inherent_to_string; +mod inline_fn_without_body; +mod int_plus_one; +mod integer_division; +mod items_after_statements; +mod large_const_arrays; +mod large_enum_variant; +mod large_stack_arrays; +mod len_zero; +mod let_if_seq; +mod let_underscore; +mod lifetimes; +mod literal_representation; +mod loops; +mod macro_use; +mod main_recursion; +mod map_clone; +mod map_unit_fn; +mod match_on_vec_items; +mod matches; +mod mem_discriminant; +mod mem_forget; +mod mem_replace; +mod methods; +mod minmax; +mod misc; +mod misc_early; +mod missing_const_for_fn; +mod missing_doc; +mod missing_inline; +mod modulo_arithmetic; +mod multiple_crate_versions; +mod mut_key; +mod mut_mut; +mod mut_reference; +mod mutable_debug_assertion; +mod mutex_atomic; +mod needless_bool; +mod needless_borrow; +mod needless_borrowed_ref; +mod needless_continue; +mod needless_pass_by_value; +mod needless_update; +mod neg_cmp_op_on_partial_ord; +mod neg_multiply; +mod new_without_default; +mod no_effect; +mod non_copy_const; +mod non_expressive_names; +mod open_options; +mod option_env_unwrap; +mod overflow_check_conditional; +mod panic_unimplemented; +mod partialeq_ne_impl; +mod path_buf_push_overwrite; +mod precedence; +mod ptr; +mod ptr_offset_with_cast; +mod question_mark; +mod ranges; +mod redundant_clone; +mod redundant_field_names; +mod redundant_pattern_matching; +mod redundant_pub_crate; +mod redundant_static_lifetimes; +mod reference; +mod regex; +mod returns; +mod serde_api; +mod shadow; +mod single_component_path_imports; +mod slow_vector_initialization; +mod strings; +mod suspicious_trait_impl; +mod swap; +mod tabs_in_doc_comments; +mod temporary_assignment; +mod to_digit_is_some; +mod trait_bounds; +mod transmute; +mod transmuting_null; +mod trivially_copy_pass_by_ref; +mod try_err; +mod types; +mod unicode; +mod unnamed_address; +mod unsafe_removed_from_name; +mod unused_io_amount; +mod unused_self; +mod unwrap; +mod use_self; +mod vec; +mod verbose_file_reads; +mod wildcard_dependencies; +mod wildcard_imports; +mod write; +mod zero_div_zero; +// end lints modules, do not remove this comment, it’s used in `update_lints` + +pub use crate::utils::conf::Conf; + +mod reexport { + pub use rustc_ast::ast::Name; +} + +/// Register all pre expansion lints +/// +/// Pre-expansion lints run before any macro expansion has happened. +/// +/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate +/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. +/// +/// Used in `./src/driver.rs`. +pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &Conf) { + store.register_pre_expansion_pass(|| box write::Write::default()); + store.register_pre_expansion_pass(|| box redundant_field_names::RedundantFieldNames); + let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; + store.register_pre_expansion_pass(move || box non_expressive_names::NonExpressiveNames { + single_char_binding_names_threshold, + }); + store.register_pre_expansion_pass(|| box attrs::EarlyAttributes); + store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro); +} + +#[doc(hidden)] +pub fn read_conf(args: &[rustc_ast::ast::NestedMetaItem], sess: &Session) -> Conf { + use std::path::Path; + match utils::conf::file_from_args(args) { + Ok(file_name) => { + // if the user specified a file, it must exist, otherwise default to `clippy.toml` but + // do not require the file to exist + let file_name = match file_name { + Some(file_name) => file_name, + None => match utils::conf::lookup_conf_file() { + Ok(Some(path)) => path, + Ok(None) => return Conf::default(), + Err(error) => { + sess.struct_err(&format!("error finding Clippy's configuration file: {}", error)) + .emit(); + return Conf::default(); + }, + }, + }; + + let file_name = if file_name.is_relative() { + sess.local_crate_source_file + .as_deref() + .and_then(Path::parent) + .unwrap_or_else(|| Path::new("")) + .join(file_name) + } else { + file_name + }; + + let (conf, errors) = utils::conf::read(&file_name); + + // all conf errors are non-fatal, we just use the default conf in case of error + for error in errors { + sess.struct_err(&format!( + "error reading Clippy's configuration file `{}`: {}", + file_name.display(), + error + )) + .emit(); + } + + conf + }, + Err((err, span)) => { + sess.struct_span_err(span, err) + .span_note(span, "Clippy will use default configuration") + .emit(); + Conf::default() + }, + } +} + +/// Register all lints and lint groups with the rustc plugin registry +/// +/// Used in `./src/driver.rs`. +#[allow(clippy::too_many_lines)] +#[rustfmt::skip] +pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { + register_removed_non_tool_lints(store); + + // begin deprecated lints, do not remove this comment, it’s used in `update_lints` + store.register_removed( + "clippy::should_assert_eq", + "`assert!()` will be more flexible with RFC 2011", + ); + store.register_removed( + "clippy::extend_from_slice", + "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", + ); + store.register_removed( + "clippy::range_step_by_zero", + "`iterator.step_by(0)` panics nowadays", + ); + store.register_removed( + "clippy::unstable_as_slice", + "`Vec::as_slice` has been stabilized in 1.7", + ); + store.register_removed( + "clippy::unstable_as_mut_slice", + "`Vec::as_mut_slice` has been stabilized in 1.7", + ); + store.register_removed( + "clippy::str_to_string", + "using `str::to_string` is common even today and specialization will likely happen soon", + ); + store.register_removed( + "clippy::string_to_string", + "using `string::to_string` is common even today and specialization will likely happen soon", + ); + store.register_removed( + "clippy::misaligned_transmute", + "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", + ); + store.register_removed( + "clippy::assign_ops", + "using compound assignment operators (e.g., `+=`) is harmless", + ); + store.register_removed( + "clippy::if_let_redundant_pattern_matching", + "this lint has been changed to redundant_pattern_matching", + ); + store.register_removed( + "clippy::unsafe_vector_initialization", + "the replacement suggested by this lint had substantially different behavior", + ); + store.register_removed( + "clippy::invalid_ref", + "superseded by rustc lint `invalid_value`", + ); + store.register_removed( + "clippy::unused_collect", + "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint", + ); + store.register_removed( + "clippy::into_iter_on_array", + "this lint has been uplifted to rustc and is now called `array_into_iter`", + ); + store.register_removed( + "clippy::unused_label", + "this lint has been uplifted to rustc and is now called `unused_labels`", + ); + store.register_removed( + "clippy::replace_consts", + "associated-constants `MIN`/`MAX` of integers are prefer to `{min,max}_value()` and module constants", + ); + // end deprecated lints, do not remove this comment, it’s used in `update_lints` + + // begin register lints, do not remove this comment, it’s used in `update_lints` + store.register_lints(&[ + &approx_const::APPROX_CONSTANT, + &arithmetic::FLOAT_ARITHMETIC, + &arithmetic::INTEGER_ARITHMETIC, + &as_conversions::AS_CONVERSIONS, + &assertions_on_constants::ASSERTIONS_ON_CONSTANTS, + &assign_ops::ASSIGN_OP_PATTERN, + &assign_ops::MISREFACTORED_ASSIGN_OP, + &atomic_ordering::INVALID_ATOMIC_ORDERING, + &attrs::DEPRECATED_CFG_ATTR, + &attrs::DEPRECATED_SEMVER, + &attrs::EMPTY_LINE_AFTER_OUTER_ATTR, + &attrs::INLINE_ALWAYS, + &attrs::MISMATCHED_TARGET_OS, + &attrs::UNKNOWN_CLIPPY_LINTS, + &attrs::USELESS_ATTRIBUTE, + &await_holding_lock::AWAIT_HOLDING_LOCK, + &bit_mask::BAD_BIT_MASK, + &bit_mask::INEFFECTIVE_BIT_MASK, + &bit_mask::VERBOSE_BIT_MASK, + &blacklisted_name::BLACKLISTED_NAME, + &block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR, + &block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT, + &booleans::LOGIC_BUG, + &booleans::NONMINIMAL_BOOL, + &bytecount::NAIVE_BYTECOUNT, + &cargo_common_metadata::CARGO_COMMON_METADATA, + &checked_conversions::CHECKED_CONVERSIONS, + &cognitive_complexity::COGNITIVE_COMPLEXITY, + &collapsible_if::COLLAPSIBLE_IF, + &comparison_chain::COMPARISON_CHAIN, + &copies::IFS_SAME_COND, + &copies::IF_SAME_THEN_ELSE, + &copies::MATCH_SAME_ARMS, + &copies::SAME_FUNCTIONS_IN_IF_CONDITION, + ©_iterator::COPY_ITERATOR, + &dbg_macro::DBG_MACRO, + &default_trait_access::DEFAULT_TRAIT_ACCESS, + &dereference::EXPLICIT_DEREF_METHODS, + &derive::DERIVE_HASH_XOR_EQ, + &derive::EXPL_IMPL_CLONE_ON_COPY, + &derive::UNSAFE_DERIVE_DESERIALIZE, + &doc::DOC_MARKDOWN, + &doc::MISSING_ERRORS_DOC, + &doc::MISSING_SAFETY_DOC, + &doc::NEEDLESS_DOCTEST_MAIN, + &double_comparison::DOUBLE_COMPARISONS, + &double_parens::DOUBLE_PARENS, + &drop_bounds::DROP_BOUNDS, + &drop_forget_ref::DROP_COPY, + &drop_forget_ref::DROP_REF, + &drop_forget_ref::FORGET_COPY, + &drop_forget_ref::FORGET_REF, + &duration_subsec::DURATION_SUBSEC, + &else_if_without_else::ELSE_IF_WITHOUT_ELSE, + &empty_enum::EMPTY_ENUM, + &entry::MAP_ENTRY, + &enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT, + &enum_variants::ENUM_VARIANT_NAMES, + &enum_variants::MODULE_INCEPTION, + &enum_variants::MODULE_NAME_REPETITIONS, + &enum_variants::PUB_ENUM_VARIANT_NAMES, + &eq_op::EQ_OP, + &eq_op::OP_REF, + &erasing_op::ERASING_OP, + &escape::BOXED_LOCAL, + &eta_reduction::REDUNDANT_CLOSURE, + &eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + &eval_order_dependence::DIVERGING_SUB_EXPRESSION, + &eval_order_dependence::EVAL_ORDER_DEPENDENCE, + &excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS, + &excessive_bools::STRUCT_EXCESSIVE_BOOLS, + &exit::EXIT, + &explicit_write::EXPLICIT_WRITE, + &fallible_impl_from::FALLIBLE_IMPL_FROM, + &float_literal::EXCESSIVE_PRECISION, + &float_literal::LOSSY_FLOAT_LITERAL, + &floating_point_arithmetic::IMPRECISE_FLOPS, + &floating_point_arithmetic::SUBOPTIMAL_FLOPS, + &format::USELESS_FORMAT, + &formatting::POSSIBLE_MISSING_COMMA, + &formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING, + &formatting::SUSPICIOUS_ELSE_FORMATTING, + &formatting::SUSPICIOUS_UNARY_OP_FORMATTING, + &functions::DOUBLE_MUST_USE, + &functions::MUST_USE_CANDIDATE, + &functions::MUST_USE_UNIT, + &functions::NOT_UNSAFE_PTR_ARG_DEREF, + &functions::TOO_MANY_ARGUMENTS, + &functions::TOO_MANY_LINES, + &future_not_send::FUTURE_NOT_SEND, + &get_last_with_len::GET_LAST_WITH_LEN, + &identity_conversion::IDENTITY_CONVERSION, + &identity_op::IDENTITY_OP, + &if_let_mutex::IF_LET_MUTEX, + &if_let_some_result::IF_LET_SOME_RESULT, + &if_not_else::IF_NOT_ELSE, + &implicit_return::IMPLICIT_RETURN, + &implicit_saturating_sub::IMPLICIT_SATURATING_SUB, + &indexing_slicing::INDEXING_SLICING, + &indexing_slicing::OUT_OF_BOUNDS_INDEXING, + &infinite_iter::INFINITE_ITER, + &infinite_iter::MAYBE_INFINITE_ITER, + &inherent_impl::MULTIPLE_INHERENT_IMPL, + &inherent_to_string::INHERENT_TO_STRING, + &inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY, + &inline_fn_without_body::INLINE_FN_WITHOUT_BODY, + &int_plus_one::INT_PLUS_ONE, + &integer_division::INTEGER_DIVISION, + &items_after_statements::ITEMS_AFTER_STATEMENTS, + &large_const_arrays::LARGE_CONST_ARRAYS, + &large_enum_variant::LARGE_ENUM_VARIANT, + &large_stack_arrays::LARGE_STACK_ARRAYS, + &len_zero::LEN_WITHOUT_IS_EMPTY, + &len_zero::LEN_ZERO, + &let_if_seq::USELESS_LET_IF_SEQ, + &let_underscore::LET_UNDERSCORE_LOCK, + &let_underscore::LET_UNDERSCORE_MUST_USE, + &lifetimes::EXTRA_UNUSED_LIFETIMES, + &lifetimes::NEEDLESS_LIFETIMES, + &literal_representation::DECIMAL_LITERAL_REPRESENTATION, + &literal_representation::INCONSISTENT_DIGIT_GROUPING, + &literal_representation::LARGE_DIGIT_GROUPS, + &literal_representation::MISTYPED_LITERAL_SUFFIXES, + &literal_representation::UNREADABLE_LITERAL, + &loops::EMPTY_LOOP, + &loops::EXPLICIT_COUNTER_LOOP, + &loops::EXPLICIT_INTO_ITER_LOOP, + &loops::EXPLICIT_ITER_LOOP, + &loops::FOR_KV_MAP, + &loops::FOR_LOOP_OVER_OPTION, + &loops::FOR_LOOP_OVER_RESULT, + &loops::ITER_NEXT_LOOP, + &loops::MANUAL_MEMCPY, + &loops::MUT_RANGE_BOUND, + &loops::NEEDLESS_COLLECT, + &loops::NEEDLESS_RANGE_LOOP, + &loops::NEVER_LOOP, + &loops::REVERSE_RANGE_LOOP, + &loops::WHILE_IMMUTABLE_CONDITION, + &loops::WHILE_LET_LOOP, + &loops::WHILE_LET_ON_ITERATOR, + ¯o_use::MACRO_USE_IMPORTS, + &main_recursion::MAIN_RECURSION, + &map_clone::MAP_CLONE, + &map_unit_fn::OPTION_MAP_UNIT_FN, + &map_unit_fn::RESULT_MAP_UNIT_FN, + &match_on_vec_items::MATCH_ON_VEC_ITEMS, + &matches::INFALLIBLE_DESTRUCTURING_MATCH, + &matches::MATCH_AS_REF, + &matches::MATCH_BOOL, + &matches::MATCH_OVERLAPPING_ARM, + &matches::MATCH_REF_PATS, + &matches::MATCH_SINGLE_BINDING, + &matches::MATCH_WILD_ERR_ARM, + &matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, + &matches::SINGLE_MATCH, + &matches::SINGLE_MATCH_ELSE, + &matches::WILDCARD_ENUM_MATCH_ARM, + &matches::WILDCARD_IN_OR_PATTERNS, + &mem_discriminant::MEM_DISCRIMINANT_NON_ENUM, + &mem_forget::MEM_FORGET, + &mem_replace::MEM_REPLACE_OPTION_WITH_NONE, + &mem_replace::MEM_REPLACE_WITH_DEFAULT, + &mem_replace::MEM_REPLACE_WITH_UNINIT, + &methods::CHARS_LAST_CMP, + &methods::CHARS_NEXT_CMP, + &methods::CLONE_DOUBLE_REF, + &methods::CLONE_ON_COPY, + &methods::CLONE_ON_REF_PTR, + &methods::EXPECT_FUN_CALL, + &methods::FILETYPE_IS_FILE, + &methods::FILTER_MAP, + &methods::FILTER_MAP_NEXT, + &methods::FILTER_NEXT, + &methods::FIND_MAP, + &methods::FLAT_MAP_IDENTITY, + &methods::GET_UNWRAP, + &methods::INEFFICIENT_TO_STRING, + &methods::INTO_ITER_ON_REF, + &methods::ITERATOR_STEP_BY_ZERO, + &methods::ITER_CLONED_COLLECT, + &methods::ITER_NTH, + &methods::ITER_NTH_ZERO, + &methods::ITER_SKIP_NEXT, + &methods::MANUAL_SATURATING_ARITHMETIC, + &methods::MAP_FLATTEN, + &methods::NEW_RET_NO_SELF, + &methods::OK_EXPECT, + &methods::OPTION_AND_THEN_SOME, + &methods::OPTION_AS_REF_DEREF, + &methods::OPTION_EXPECT_USED, + &methods::OPTION_MAP_OR_NONE, + &methods::OPTION_MAP_UNWRAP_OR, + &methods::OPTION_MAP_UNWRAP_OR_ELSE, + &methods::OPTION_UNWRAP_USED, + &methods::OR_FUN_CALL, + &methods::RESULT_EXPECT_USED, + &methods::RESULT_MAP_OR_INTO_OPTION, + &methods::RESULT_MAP_UNWRAP_OR_ELSE, + &methods::RESULT_UNWRAP_USED, + &methods::SEARCH_IS_SOME, + &methods::SHOULD_IMPLEMENT_TRAIT, + &methods::SINGLE_CHAR_PATTERN, + &methods::SKIP_WHILE_NEXT, + &methods::STRING_EXTEND_CHARS, + &methods::SUSPICIOUS_MAP, + &methods::TEMPORARY_CSTRING_AS_PTR, + &methods::UNINIT_ASSUMED_INIT, + &methods::UNNECESSARY_FILTER_MAP, + &methods::UNNECESSARY_FOLD, + &methods::USELESS_ASREF, + &methods::WRONG_PUB_SELF_CONVENTION, + &methods::WRONG_SELF_CONVENTION, + &methods::ZST_OFFSET, + &minmax::MIN_MAX, + &misc::CMP_NAN, + &misc::CMP_OWNED, + &misc::FLOAT_CMP, + &misc::FLOAT_CMP_CONST, + &misc::MODULO_ONE, + &misc::SHORT_CIRCUIT_STATEMENT, + &misc::TOPLEVEL_REF_ARG, + &misc::USED_UNDERSCORE_BINDING, + &misc::ZERO_PTR, + &misc_early::BUILTIN_TYPE_SHADOW, + &misc_early::DOUBLE_NEG, + &misc_early::DUPLICATE_UNDERSCORE_ARGUMENT, + &misc_early::MIXED_CASE_HEX_LITERALS, + &misc_early::REDUNDANT_CLOSURE_CALL, + &misc_early::REDUNDANT_PATTERN, + &misc_early::UNNEEDED_FIELD_PATTERN, + &misc_early::UNNEEDED_WILDCARD_PATTERN, + &misc_early::UNSEPARATED_LITERAL_SUFFIX, + &misc_early::ZERO_PREFIXED_LITERAL, + &missing_const_for_fn::MISSING_CONST_FOR_FN, + &missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS, + &missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS, + &modulo_arithmetic::MODULO_ARITHMETIC, + &multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, + &mut_key::MUTABLE_KEY_TYPE, + &mut_mut::MUT_MUT, + &mut_reference::UNNECESSARY_MUT_PASSED, + &mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL, + &mutex_atomic::MUTEX_ATOMIC, + &mutex_atomic::MUTEX_INTEGER, + &needless_bool::BOOL_COMPARISON, + &needless_bool::NEEDLESS_BOOL, + &needless_borrow::NEEDLESS_BORROW, + &needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, + &needless_continue::NEEDLESS_CONTINUE, + &needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, + &needless_update::NEEDLESS_UPDATE, + &neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD, + &neg_multiply::NEG_MULTIPLY, + &new_without_default::NEW_WITHOUT_DEFAULT, + &no_effect::NO_EFFECT, + &no_effect::UNNECESSARY_OPERATION, + &non_copy_const::BORROW_INTERIOR_MUTABLE_CONST, + &non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST, + &non_expressive_names::JUST_UNDERSCORES_AND_DIGITS, + &non_expressive_names::MANY_SINGLE_CHAR_NAMES, + &non_expressive_names::SIMILAR_NAMES, + &open_options::NONSENSICAL_OPEN_OPTIONS, + &option_env_unwrap::OPTION_ENV_UNWRAP, + &overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL, + &panic_unimplemented::PANIC, + &panic_unimplemented::PANIC_PARAMS, + &panic_unimplemented::TODO, + &panic_unimplemented::UNIMPLEMENTED, + &panic_unimplemented::UNREACHABLE, + &partialeq_ne_impl::PARTIALEQ_NE_IMPL, + &path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, + &precedence::PRECEDENCE, + &ptr::CMP_NULL, + &ptr::MUT_FROM_REF, + &ptr::PTR_ARG, + &ptr_offset_with_cast::PTR_OFFSET_WITH_CAST, + &question_mark::QUESTION_MARK, + &ranges::RANGE_MINUS_ONE, + &ranges::RANGE_PLUS_ONE, + &ranges::RANGE_ZIP_WITH_LEN, + &redundant_clone::REDUNDANT_CLONE, + &redundant_field_names::REDUNDANT_FIELD_NAMES, + &redundant_pattern_matching::REDUNDANT_PATTERN_MATCHING, + &redundant_pub_crate::REDUNDANT_PUB_CRATE, + &redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES, + &reference::DEREF_ADDROF, + &reference::REF_IN_DEREF, + ®ex::INVALID_REGEX, + ®ex::REGEX_MACRO, + ®ex::TRIVIAL_REGEX, + &returns::LET_AND_RETURN, + &returns::NEEDLESS_RETURN, + &returns::UNUSED_UNIT, + &serde_api::SERDE_API_MISUSE, + &shadow::SHADOW_REUSE, + &shadow::SHADOW_SAME, + &shadow::SHADOW_UNRELATED, + &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, + &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, + &strings::STRING_ADD, + &strings::STRING_ADD_ASSIGN, + &strings::STRING_LIT_AS_BYTES, + &suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL, + &suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL, + &swap::ALMOST_SWAPPED, + &swap::MANUAL_SWAP, + &tabs_in_doc_comments::TABS_IN_DOC_COMMENTS, + &temporary_assignment::TEMPORARY_ASSIGNMENT, + &to_digit_is_some::TO_DIGIT_IS_SOME, + &trait_bounds::TYPE_REPETITION_IN_BOUNDS, + &transmute::CROSSPOINTER_TRANSMUTE, + &transmute::TRANSMUTE_BYTES_TO_STR, + &transmute::TRANSMUTE_FLOAT_TO_INT, + &transmute::TRANSMUTE_INT_TO_BOOL, + &transmute::TRANSMUTE_INT_TO_CHAR, + &transmute::TRANSMUTE_INT_TO_FLOAT, + &transmute::TRANSMUTE_PTR_TO_PTR, + &transmute::TRANSMUTE_PTR_TO_REF, + &transmute::UNSOUND_COLLECTION_TRANSMUTE, + &transmute::USELESS_TRANSMUTE, + &transmute::WRONG_TRANSMUTE, + &transmuting_null::TRANSMUTING_NULL, + &trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF, + &try_err::TRY_ERR, + &types::ABSURD_EXTREME_COMPARISONS, + &types::BORROWED_BOX, + &types::BOX_VEC, + &types::CAST_LOSSLESS, + &types::CAST_POSSIBLE_TRUNCATION, + &types::CAST_POSSIBLE_WRAP, + &types::CAST_PRECISION_LOSS, + &types::CAST_PTR_ALIGNMENT, + &types::CAST_REF_TO_MUT, + &types::CAST_SIGN_LOSS, + &types::CHAR_LIT_AS_U8, + &types::FN_TO_NUMERIC_CAST, + &types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + &types::IMPLICIT_HASHER, + &types::INVALID_UPCAST_COMPARISONS, + &types::LET_UNIT_VALUE, + &types::LINKEDLIST, + &types::OPTION_OPTION, + &types::REDUNDANT_ALLOCATION, + &types::TYPE_COMPLEXITY, + &types::UNIT_ARG, + &types::UNIT_CMP, + &types::UNNECESSARY_CAST, + &types::VEC_BOX, + &unicode::NON_ASCII_LITERAL, + &unicode::UNICODE_NOT_NFC, + &unicode::ZERO_WIDTH_SPACE, + &unnamed_address::FN_ADDRESS_COMPARISONS, + &unnamed_address::VTABLE_ADDRESS_COMPARISONS, + &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, + &unused_io_amount::UNUSED_IO_AMOUNT, + &unused_self::UNUSED_SELF, + &unwrap::PANICKING_UNWRAP, + &unwrap::UNNECESSARY_UNWRAP, + &use_self::USE_SELF, + &utils::internal_lints::CLIPPY_LINTS_INTERNAL, + &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS, + &utils::internal_lints::COMPILER_LINT_FUNCTIONS, + &utils::internal_lints::DEFAULT_LINT, + &utils::internal_lints::LINT_WITHOUT_LINT_PASS, + &utils::internal_lints::OUTER_EXPN_EXPN_DATA, + &utils::internal_lints::PRODUCE_ICE, + &vec::USELESS_VEC, + &verbose_file_reads::VERBOSE_FILE_READS, + &wildcard_dependencies::WILDCARD_DEPENDENCIES, + &wildcard_imports::ENUM_GLOB_USE, + &wildcard_imports::WILDCARD_IMPORTS, + &write::PRINTLN_EMPTY_STRING, + &write::PRINT_LITERAL, + &write::PRINT_STDOUT, + &write::PRINT_WITH_NEWLINE, + &write::USE_DEBUG, + &write::WRITELN_EMPTY_STRING, + &write::WRITE_LITERAL, + &write::WRITE_WITH_NEWLINE, + &zero_div_zero::ZERO_DIVIDED_BY_ZERO, + ]); + // end register lints, do not remove this comment, it’s used in `update_lints` + + store.register_late_pass(|| box await_holding_lock::AwaitHoldingLock); + store.register_late_pass(|| box serde_api::SerdeAPI); + store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new()); + store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default()); + store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass); + store.register_late_pass(|| box utils::inspector::DeepCodeInspector); + store.register_late_pass(|| box utils::author::Author); + let vec_box_size_threshold = conf.vec_box_size_threshold; + store.register_late_pass(move || box types::Types::new(vec_box_size_threshold)); + store.register_late_pass(|| box booleans::NonminimalBool); + store.register_late_pass(|| box eq_op::EqOp); + store.register_late_pass(|| box enum_clike::UnportableVariant); + store.register_late_pass(|| box float_literal::FloatLiteral); + let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; + store.register_late_pass(move || box bit_mask::BitMask::new(verbose_bit_mask_threshold)); + store.register_late_pass(|| box ptr::Ptr); + store.register_late_pass(|| box needless_bool::NeedlessBool); + store.register_late_pass(|| box needless_bool::BoolComparison); + store.register_late_pass(|| box approx_const::ApproxConstant); + store.register_late_pass(|| box misc::MiscLints); + store.register_late_pass(|| box eta_reduction::EtaReduction); + store.register_late_pass(|| box identity_op::IdentityOp); + store.register_late_pass(|| box erasing_op::ErasingOp); + store.register_late_pass(|| box mut_mut::MutMut); + store.register_late_pass(|| box mut_reference::UnnecessaryMutPassed); + store.register_late_pass(|| box len_zero::LenZero); + store.register_late_pass(|| box attrs::Attributes); + store.register_late_pass(|| box block_in_if_condition::BlockInIfCondition); + store.register_late_pass(|| box unicode::Unicode); + store.register_late_pass(|| box strings::StringAdd); + store.register_late_pass(|| box implicit_return::ImplicitReturn); + store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub); + store.register_late_pass(|| box methods::Methods); + store.register_late_pass(|| box map_clone::MapClone); + store.register_late_pass(|| box shadow::Shadow); + store.register_late_pass(|| box types::LetUnitValue); + store.register_late_pass(|| box types::UnitCmp); + store.register_late_pass(|| box loops::Loops); + store.register_late_pass(|| box main_recursion::MainRecursion::default()); + store.register_late_pass(|| box lifetimes::Lifetimes); + store.register_late_pass(|| box entry::HashMapPass); + store.register_late_pass(|| box ranges::Ranges); + store.register_late_pass(|| box types::Casts); + let type_complexity_threshold = conf.type_complexity_threshold; + store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold)); + store.register_late_pass(|| box matches::Matches::default()); + store.register_late_pass(|| box minmax::MinMaxPass); + store.register_late_pass(|| box open_options::OpenOptions); + store.register_late_pass(|| box zero_div_zero::ZeroDiv); + store.register_late_pass(|| box mutex_atomic::Mutex); + store.register_late_pass(|| box needless_update::NeedlessUpdate); + store.register_late_pass(|| box needless_borrow::NeedlessBorrow::default()); + store.register_late_pass(|| box needless_borrowed_ref::NeedlessBorrowedRef); + store.register_late_pass(|| box no_effect::NoEffect); + store.register_late_pass(|| box temporary_assignment::TemporaryAssignment); + store.register_late_pass(|| box transmute::Transmute); + let cognitive_complexity_threshold = conf.cognitive_complexity_threshold; + store.register_late_pass(move || box cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold)); + let too_large_for_stack = conf.too_large_for_stack; + store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack}); + store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented); + store.register_late_pass(|| box strings::StringLitAsBytes); + store.register_late_pass(|| box derive::Derive); + store.register_late_pass(|| box types::CharLitAsU8); + store.register_late_pass(|| box vec::UselessVec); + store.register_late_pass(|| box drop_bounds::DropBounds); + store.register_late_pass(|| box get_last_with_len::GetLastWithLen); + store.register_late_pass(|| box drop_forget_ref::DropForgetRef); + store.register_late_pass(|| box empty_enum::EmptyEnum); + store.register_late_pass(|| box types::AbsurdExtremeComparisons); + store.register_late_pass(|| box types::InvalidUpcastComparisons); + store.register_late_pass(|| box regex::Regex::default()); + store.register_late_pass(|| box copies::CopyAndPaste); + store.register_late_pass(|| box copy_iterator::CopyIterator); + store.register_late_pass(|| box format::UselessFormat); + store.register_late_pass(|| box swap::Swap); + store.register_late_pass(|| box overflow_check_conditional::OverflowCheckConditional); + store.register_late_pass(|| box new_without_default::NewWithoutDefault::default()); + let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::>(); + store.register_late_pass(move || box blacklisted_name::BlacklistedName::new(blacklisted_names.clone())); + let too_many_arguments_threshold1 = conf.too_many_arguments_threshold; + let too_many_lines_threshold2 = conf.too_many_lines_threshold; + store.register_late_pass(move || box functions::Functions::new(too_many_arguments_threshold1, too_many_lines_threshold2)); + let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::>(); + store.register_late_pass(move || box doc::DocMarkdown::new(doc_valid_idents.clone())); + store.register_late_pass(|| box neg_multiply::NegMultiply); + store.register_late_pass(|| box mem_discriminant::MemDiscriminant); + store.register_late_pass(|| box mem_forget::MemForget); + store.register_late_pass(|| box mem_replace::MemReplace); + store.register_late_pass(|| box arithmetic::Arithmetic::default()); + store.register_late_pass(|| box assign_ops::AssignOps); + store.register_late_pass(|| box let_if_seq::LetIfSeq); + store.register_late_pass(|| box eval_order_dependence::EvalOrderDependence); + store.register_late_pass(|| box missing_doc::MissingDoc::new()); + store.register_late_pass(|| box missing_inline::MissingInline); + store.register_late_pass(|| box if_let_some_result::OkIfLet); + store.register_late_pass(|| box redundant_pattern_matching::RedundantPatternMatching); + store.register_late_pass(|| box partialeq_ne_impl::PartialEqNeImpl); + store.register_late_pass(|| box unused_io_amount::UnusedIoAmount); + let enum_variant_size_threshold = conf.enum_variant_size_threshold; + store.register_late_pass(move || box large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)); + store.register_late_pass(|| box explicit_write::ExplicitWrite); + store.register_late_pass(|| box needless_pass_by_value::NeedlessPassByValue); + let trivially_copy_pass_by_ref = trivially_copy_pass_by_ref::TriviallyCopyPassByRef::new( + conf.trivial_copy_size_limit, + &sess.target, + ); + store.register_late_pass(move || box trivially_copy_pass_by_ref); + store.register_late_pass(|| box try_err::TryErr); + store.register_late_pass(|| box use_self::UseSelf); + store.register_late_pass(|| box bytecount::ByteCount); + store.register_late_pass(|| box infinite_iter::InfiniteIter); + store.register_late_pass(|| box inline_fn_without_body::InlineFnWithoutBody); + store.register_late_pass(|| box identity_conversion::IdentityConversion::default()); + store.register_late_pass(|| box types::ImplicitHasher); + store.register_late_pass(|| box fallible_impl_from::FallibleImplFrom); + store.register_late_pass(|| box types::UnitArg); + store.register_late_pass(|| box double_comparison::DoubleComparisons); + store.register_late_pass(|| box question_mark::QuestionMark); + store.register_late_pass(|| box suspicious_trait_impl::SuspiciousImpl); + store.register_late_pass(|| box map_unit_fn::MapUnit); + store.register_late_pass(|| box inherent_impl::MultipleInherentImpl::default()); + store.register_late_pass(|| box neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd); + store.register_late_pass(|| box unwrap::Unwrap); + store.register_late_pass(|| box duration_subsec::DurationSubsec); + store.register_late_pass(|| box default_trait_access::DefaultTraitAccess); + store.register_late_pass(|| box indexing_slicing::IndexingSlicing); + store.register_late_pass(|| box non_copy_const::NonCopyConst); + store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast); + store.register_late_pass(|| box redundant_clone::RedundantClone); + store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); + store.register_late_pass(|| box types::RefToMut); + store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); + store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn); + store.register_late_pass(|| box transmuting_null::TransmutingNull); + store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite); + store.register_late_pass(|| box checked_conversions::CheckedConversions); + store.register_late_pass(|| box integer_division::IntegerDivision); + store.register_late_pass(|| box inherent_to_string::InherentToString); + store.register_late_pass(|| box trait_bounds::TraitBounds); + store.register_late_pass(|| box comparison_chain::ComparisonChain); + store.register_late_pass(|| box mut_key::MutableKeyType); + store.register_late_pass(|| box modulo_arithmetic::ModuloArithmetic); + store.register_early_pass(|| box reference::DerefAddrOf); + store.register_early_pass(|| box reference::RefInDeref); + store.register_early_pass(|| box double_parens::DoubleParens); + store.register_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval); + store.register_early_pass(|| box if_not_else::IfNotElse); + store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse); + store.register_early_pass(|| box int_plus_one::IntPlusOne); + store.register_early_pass(|| box formatting::Formatting); + store.register_early_pass(|| box misc_early::MiscEarlyLints); + store.register_early_pass(|| box returns::Return); + store.register_early_pass(|| box collapsible_if::CollapsibleIf); + store.register_early_pass(|| box items_after_statements::ItemsAfterStatements); + store.register_early_pass(|| box precedence::Precedence); + store.register_early_pass(|| box needless_continue::NeedlessContinue); + store.register_early_pass(|| box redundant_static_lifetimes::RedundantStaticLifetimes); + store.register_late_pass(|| box cargo_common_metadata::CargoCommonMetadata); + store.register_late_pass(|| box multiple_crate_versions::MultipleCrateVersions); + store.register_late_pass(|| box wildcard_dependencies::WildcardDependencies); + store.register_early_pass(|| box literal_representation::LiteralDigitGrouping); + let literal_representation_threshold = conf.literal_representation_threshold; + store.register_early_pass(move || box literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold)); + store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal); + let enum_variant_name_threshold = conf.enum_variant_name_threshold; + store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold)); + store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments); + store.register_late_pass(|| box unused_self::UnusedSelf); + store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall); + store.register_late_pass(|| box exit::Exit); + store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome); + let array_size_threshold = conf.array_size_threshold; + store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold)); + store.register_late_pass(move || box large_const_arrays::LargeConstArrays::new(array_size_threshold)); + store.register_late_pass(move || box floating_point_arithmetic::FloatingPointArithmetic); + store.register_early_pass(|| box as_conversions::AsConversions); + store.register_early_pass(|| box utils::internal_lints::ProduceIce); + store.register_late_pass(|| box let_underscore::LetUnderscore); + store.register_late_pass(|| box atomic_ordering::AtomicOrdering); + store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports); + let max_fn_params_bools = conf.max_fn_params_bools; + let max_struct_bools = conf.max_struct_bools; + store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools)); + store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap); + store.register_late_pass(|| box wildcard_imports::WildcardImports); + store.register_early_pass(|| box macro_use::MacroUseImports); + store.register_late_pass(|| box verbose_file_reads::VerboseFileReads); + store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default()); + store.register_late_pass(|| box unnamed_address::UnnamedAddress); + store.register_late_pass(|| box dereference::Dereferencing); + store.register_late_pass(|| box future_not_send::FutureNotSend); + store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls); + store.register_late_pass(|| box if_let_mutex::IfLetMutex); + store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); + + store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ + LintId::of(&arithmetic::FLOAT_ARITHMETIC), + LintId::of(&arithmetic::INTEGER_ARITHMETIC), + LintId::of(&as_conversions::AS_CONVERSIONS), + LintId::of(&dbg_macro::DBG_MACRO), + LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE), + LintId::of(&exit::EXIT), + LintId::of(&float_literal::LOSSY_FLOAT_LITERAL), + LintId::of(&implicit_return::IMPLICIT_RETURN), + LintId::of(&indexing_slicing::INDEXING_SLICING), + LintId::of(&inherent_impl::MULTIPLE_INHERENT_IMPL), + LintId::of(&integer_division::INTEGER_DIVISION), + LintId::of(&let_underscore::LET_UNDERSCORE_MUST_USE), + LintId::of(&literal_representation::DECIMAL_LITERAL_REPRESENTATION), + LintId::of(&matches::REST_PAT_IN_FULLY_BOUND_STRUCTS), + LintId::of(&matches::WILDCARD_ENUM_MATCH_ARM), + LintId::of(&mem_forget::MEM_FORGET), + LintId::of(&methods::CLONE_ON_REF_PTR), + LintId::of(&methods::FILETYPE_IS_FILE), + LintId::of(&methods::GET_UNWRAP), + LintId::of(&methods::OPTION_EXPECT_USED), + LintId::of(&methods::OPTION_UNWRAP_USED), + LintId::of(&methods::RESULT_EXPECT_USED), + LintId::of(&methods::RESULT_UNWRAP_USED), + LintId::of(&methods::WRONG_PUB_SELF_CONVENTION), + LintId::of(&misc::FLOAT_CMP_CONST), + LintId::of(&misc_early::UNNEEDED_FIELD_PATTERN), + LintId::of(&missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS), + LintId::of(&missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS), + LintId::of(&modulo_arithmetic::MODULO_ARITHMETIC), + LintId::of(&panic_unimplemented::PANIC), + LintId::of(&panic_unimplemented::TODO), + LintId::of(&panic_unimplemented::UNIMPLEMENTED), + LintId::of(&panic_unimplemented::UNREACHABLE), + LintId::of(&shadow::SHADOW_REUSE), + LintId::of(&shadow::SHADOW_SAME), + LintId::of(&strings::STRING_ADD), + LintId::of(&verbose_file_reads::VERBOSE_FILE_READS), + LintId::of(&write::PRINT_STDOUT), + LintId::of(&write::USE_DEBUG), + ]); + + store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ + LintId::of(&attrs::INLINE_ALWAYS), + LintId::of(&await_holding_lock::AWAIT_HOLDING_LOCK), + LintId::of(&checked_conversions::CHECKED_CONVERSIONS), + LintId::of(&copies::MATCH_SAME_ARMS), + LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION), + LintId::of(©_iterator::COPY_ITERATOR), + LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS), + LintId::of(&dereference::EXPLICIT_DEREF_METHODS), + LintId::of(&derive::EXPL_IMPL_CLONE_ON_COPY), + LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE), + LintId::of(&doc::DOC_MARKDOWN), + LintId::of(&doc::MISSING_ERRORS_DOC), + LintId::of(&empty_enum::EMPTY_ENUM), + LintId::of(&enum_variants::MODULE_NAME_REPETITIONS), + LintId::of(&enum_variants::PUB_ENUM_VARIANT_NAMES), + LintId::of(&eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS), + LintId::of(&excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS), + LintId::of(&excessive_bools::STRUCT_EXCESSIVE_BOOLS), + LintId::of(&functions::MUST_USE_CANDIDATE), + LintId::of(&functions::TOO_MANY_LINES), + LintId::of(&if_not_else::IF_NOT_ELSE), + LintId::of(&implicit_saturating_sub::IMPLICIT_SATURATING_SUB), + LintId::of(&infinite_iter::MAYBE_INFINITE_ITER), + LintId::of(&items_after_statements::ITEMS_AFTER_STATEMENTS), + LintId::of(&large_stack_arrays::LARGE_STACK_ARRAYS), + LintId::of(&literal_representation::LARGE_DIGIT_GROUPS), + LintId::of(&literal_representation::UNREADABLE_LITERAL), + LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP), + LintId::of(&loops::EXPLICIT_ITER_LOOP), + LintId::of(¯o_use::MACRO_USE_IMPORTS), + LintId::of(&matches::MATCH_BOOL), + LintId::of(&matches::SINGLE_MATCH_ELSE), + LintId::of(&methods::FILTER_MAP), + LintId::of(&methods::FILTER_MAP_NEXT), + LintId::of(&methods::FIND_MAP), + LintId::of(&methods::INEFFICIENT_TO_STRING), + LintId::of(&methods::MAP_FLATTEN), + LintId::of(&methods::OPTION_MAP_UNWRAP_OR), + LintId::of(&methods::OPTION_MAP_UNWRAP_OR_ELSE), + LintId::of(&methods::RESULT_MAP_UNWRAP_OR_ELSE), + LintId::of(&misc::USED_UNDERSCORE_BINDING), + LintId::of(&misc_early::UNSEPARATED_LITERAL_SUFFIX), + LintId::of(&mut_mut::MUT_MUT), + LintId::of(&needless_continue::NEEDLESS_CONTINUE), + LintId::of(&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), + LintId::of(&non_expressive_names::SIMILAR_NAMES), + LintId::of(&ranges::RANGE_PLUS_ONE), + LintId::of(&shadow::SHADOW_UNRELATED), + LintId::of(&strings::STRING_ADD_ASSIGN), + LintId::of(&trait_bounds::TYPE_REPETITION_IN_BOUNDS), + LintId::of(&trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF), + LintId::of(&types::CAST_LOSSLESS), + LintId::of(&types::CAST_POSSIBLE_TRUNCATION), + LintId::of(&types::CAST_POSSIBLE_WRAP), + LintId::of(&types::CAST_PRECISION_LOSS), + LintId::of(&types::CAST_SIGN_LOSS), + LintId::of(&types::IMPLICIT_HASHER), + LintId::of(&types::INVALID_UPCAST_COMPARISONS), + LintId::of(&types::LET_UNIT_VALUE), + LintId::of(&types::LINKEDLIST), + LintId::of(&types::OPTION_OPTION), + LintId::of(&unicode::NON_ASCII_LITERAL), + LintId::of(&unicode::UNICODE_NOT_NFC), + LintId::of(&unused_self::UNUSED_SELF), + LintId::of(&wildcard_imports::ENUM_GLOB_USE), + LintId::of(&wildcard_imports::WILDCARD_IMPORTS), + ]); + + store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![ + LintId::of(&utils::internal_lints::CLIPPY_LINTS_INTERNAL), + LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS), + LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS), + LintId::of(&utils::internal_lints::DEFAULT_LINT), + LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS), + LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA), + LintId::of(&utils::internal_lints::PRODUCE_ICE), + ]); + + store.register_group(true, "clippy::all", Some("clippy"), vec![ + LintId::of(&approx_const::APPROX_CONSTANT), + LintId::of(&assertions_on_constants::ASSERTIONS_ON_CONSTANTS), + LintId::of(&assign_ops::ASSIGN_OP_PATTERN), + LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP), + LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING), + LintId::of(&attrs::DEPRECATED_CFG_ATTR), + LintId::of(&attrs::DEPRECATED_SEMVER), + LintId::of(&attrs::MISMATCHED_TARGET_OS), + LintId::of(&attrs::UNKNOWN_CLIPPY_LINTS), + LintId::of(&attrs::USELESS_ATTRIBUTE), + LintId::of(&bit_mask::BAD_BIT_MASK), + LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), + LintId::of(&bit_mask::VERBOSE_BIT_MASK), + LintId::of(&blacklisted_name::BLACKLISTED_NAME), + LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR), + LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT), + LintId::of(&booleans::LOGIC_BUG), + LintId::of(&booleans::NONMINIMAL_BOOL), + LintId::of(&bytecount::NAIVE_BYTECOUNT), + LintId::of(&collapsible_if::COLLAPSIBLE_IF), + LintId::of(&comparison_chain::COMPARISON_CHAIN), + LintId::of(&copies::IFS_SAME_COND), + LintId::of(&copies::IF_SAME_THEN_ELSE), + LintId::of(&derive::DERIVE_HASH_XOR_EQ), + LintId::of(&doc::MISSING_SAFETY_DOC), + LintId::of(&doc::NEEDLESS_DOCTEST_MAIN), + LintId::of(&double_comparison::DOUBLE_COMPARISONS), + LintId::of(&double_parens::DOUBLE_PARENS), + LintId::of(&drop_bounds::DROP_BOUNDS), + LintId::of(&drop_forget_ref::DROP_COPY), + LintId::of(&drop_forget_ref::DROP_REF), + LintId::of(&drop_forget_ref::FORGET_COPY), + LintId::of(&drop_forget_ref::FORGET_REF), + LintId::of(&duration_subsec::DURATION_SUBSEC), + LintId::of(&entry::MAP_ENTRY), + LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), + LintId::of(&enum_variants::ENUM_VARIANT_NAMES), + LintId::of(&enum_variants::MODULE_INCEPTION), + LintId::of(&eq_op::EQ_OP), + LintId::of(&eq_op::OP_REF), + LintId::of(&erasing_op::ERASING_OP), + LintId::of(&escape::BOXED_LOCAL), + LintId::of(&eta_reduction::REDUNDANT_CLOSURE), + LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION), + LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE), + LintId::of(&explicit_write::EXPLICIT_WRITE), + LintId::of(&float_literal::EXCESSIVE_PRECISION), + LintId::of(&format::USELESS_FORMAT), + LintId::of(&formatting::POSSIBLE_MISSING_COMMA), + LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING), + LintId::of(&functions::DOUBLE_MUST_USE), + LintId::of(&functions::MUST_USE_UNIT), + LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF), + LintId::of(&functions::TOO_MANY_ARGUMENTS), + LintId::of(&get_last_with_len::GET_LAST_WITH_LEN), + LintId::of(&identity_conversion::IDENTITY_CONVERSION), + LintId::of(&identity_op::IDENTITY_OP), + LintId::of(&if_let_mutex::IF_LET_MUTEX), + LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), + LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING), + LintId::of(&infinite_iter::INFINITE_ITER), + LintId::of(&inherent_to_string::INHERENT_TO_STRING), + LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY), + LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY), + LintId::of(&int_plus_one::INT_PLUS_ONE), + LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS), + LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), + LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), + LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_if_seq::USELESS_LET_IF_SEQ), + LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), + LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), + LintId::of(&lifetimes::NEEDLESS_LIFETIMES), + LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), + LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), + LintId::of(&loops::EMPTY_LOOP), + LintId::of(&loops::EXPLICIT_COUNTER_LOOP), + LintId::of(&loops::FOR_KV_MAP), + LintId::of(&loops::FOR_LOOP_OVER_OPTION), + LintId::of(&loops::FOR_LOOP_OVER_RESULT), + LintId::of(&loops::ITER_NEXT_LOOP), + LintId::of(&loops::MANUAL_MEMCPY), + LintId::of(&loops::MUT_RANGE_BOUND), + LintId::of(&loops::NEEDLESS_COLLECT), + LintId::of(&loops::NEEDLESS_RANGE_LOOP), + LintId::of(&loops::NEVER_LOOP), + LintId::of(&loops::REVERSE_RANGE_LOOP), + LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), + LintId::of(&loops::WHILE_LET_LOOP), + LintId::of(&loops::WHILE_LET_ON_ITERATOR), + LintId::of(&main_recursion::MAIN_RECURSION), + LintId::of(&map_clone::MAP_CLONE), + LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), + LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN), + LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), + LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), + LintId::of(&matches::MATCH_AS_REF), + LintId::of(&matches::MATCH_OVERLAPPING_ARM), + LintId::of(&matches::MATCH_REF_PATS), + LintId::of(&matches::MATCH_SINGLE_BINDING), + LintId::of(&matches::MATCH_WILD_ERR_ARM), + LintId::of(&matches::SINGLE_MATCH), + LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), + LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), + LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), + LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), + LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), + LintId::of(&methods::CHARS_LAST_CMP), + LintId::of(&methods::CHARS_NEXT_CMP), + LintId::of(&methods::CLONE_DOUBLE_REF), + LintId::of(&methods::CLONE_ON_COPY), + LintId::of(&methods::EXPECT_FUN_CALL), + LintId::of(&methods::FILTER_NEXT), + LintId::of(&methods::FLAT_MAP_IDENTITY), + LintId::of(&methods::INTO_ITER_ON_REF), + LintId::of(&methods::ITERATOR_STEP_BY_ZERO), + LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NTH), + LintId::of(&methods::ITER_NTH_ZERO), + LintId::of(&methods::ITER_SKIP_NEXT), + LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), + LintId::of(&methods::NEW_RET_NO_SELF), + LintId::of(&methods::OK_EXPECT), + LintId::of(&methods::OPTION_AND_THEN_SOME), + LintId::of(&methods::OPTION_AS_REF_DEREF), + LintId::of(&methods::OPTION_MAP_OR_NONE), + LintId::of(&methods::OR_FUN_CALL), + LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION), + LintId::of(&methods::SEARCH_IS_SOME), + LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT), + LintId::of(&methods::SINGLE_CHAR_PATTERN), + LintId::of(&methods::SKIP_WHILE_NEXT), + LintId::of(&methods::STRING_EXTEND_CHARS), + LintId::of(&methods::SUSPICIOUS_MAP), + LintId::of(&methods::TEMPORARY_CSTRING_AS_PTR), + LintId::of(&methods::UNINIT_ASSUMED_INIT), + LintId::of(&methods::UNNECESSARY_FILTER_MAP), + LintId::of(&methods::UNNECESSARY_FOLD), + LintId::of(&methods::USELESS_ASREF), + LintId::of(&methods::WRONG_SELF_CONVENTION), + LintId::of(&methods::ZST_OFFSET), + LintId::of(&minmax::MIN_MAX), + LintId::of(&misc::CMP_NAN), + LintId::of(&misc::CMP_OWNED), + LintId::of(&misc::FLOAT_CMP), + LintId::of(&misc::MODULO_ONE), + LintId::of(&misc::SHORT_CIRCUIT_STATEMENT), + LintId::of(&misc::TOPLEVEL_REF_ARG), + LintId::of(&misc::ZERO_PTR), + LintId::of(&misc_early::BUILTIN_TYPE_SHADOW), + LintId::of(&misc_early::DOUBLE_NEG), + LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), + LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), + LintId::of(&misc_early::REDUNDANT_CLOSURE_CALL), + LintId::of(&misc_early::REDUNDANT_PATTERN), + LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN), + LintId::of(&misc_early::ZERO_PREFIXED_LITERAL), + LintId::of(&mut_key::MUTABLE_KEY_TYPE), + LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), + LintId::of(&mutex_atomic::MUTEX_ATOMIC), + LintId::of(&needless_bool::BOOL_COMPARISON), + LintId::of(&needless_bool::NEEDLESS_BOOL), + LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), + LintId::of(&needless_update::NEEDLESS_UPDATE), + LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), + LintId::of(&neg_multiply::NEG_MULTIPLY), + LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), + LintId::of(&no_effect::NO_EFFECT), + LintId::of(&no_effect::UNNECESSARY_OPERATION), + LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), + LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST), + LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), + LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES), + LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP), + LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), + LintId::of(&panic_unimplemented::PANIC_PARAMS), + LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL), + LintId::of(&precedence::PRECEDENCE), + LintId::of(&ptr::CMP_NULL), + LintId::of(&ptr::MUT_FROM_REF), + LintId::of(&ptr::PTR_ARG), + LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), + LintId::of(&question_mark::QUESTION_MARK), + LintId::of(&ranges::RANGE_MINUS_ONE), + LintId::of(&ranges::RANGE_ZIP_WITH_LEN), + LintId::of(&redundant_clone::REDUNDANT_CLONE), + LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), + LintId::of(&redundant_pattern_matching::REDUNDANT_PATTERN_MATCHING), + LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), + LintId::of(&reference::DEREF_ADDROF), + LintId::of(&reference::REF_IN_DEREF), + LintId::of(®ex::INVALID_REGEX), + LintId::of(®ex::REGEX_MACRO), + LintId::of(®ex::TRIVIAL_REGEX), + LintId::of(&returns::LET_AND_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), + LintId::of(&returns::UNUSED_UNIT), + LintId::of(&serde_api::SERDE_API_MISUSE), + LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), + LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), + LintId::of(&strings::STRING_LIT_AS_BYTES), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), + LintId::of(&swap::ALMOST_SWAPPED), + LintId::of(&swap::MANUAL_SWAP), + LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), + LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), + LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), + LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), + LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR), + LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT), + LintId::of(&transmute::TRANSMUTE_INT_TO_BOOL), + LintId::of(&transmute::TRANSMUTE_INT_TO_CHAR), + LintId::of(&transmute::TRANSMUTE_INT_TO_FLOAT), + LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR), + LintId::of(&transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE), + LintId::of(&transmute::WRONG_TRANSMUTE), + LintId::of(&transmuting_null::TRANSMUTING_NULL), + LintId::of(&try_err::TRY_ERR), + LintId::of(&types::ABSURD_EXTREME_COMPARISONS), + LintId::of(&types::BORROWED_BOX), + LintId::of(&types::BOX_VEC), + LintId::of(&types::CAST_PTR_ALIGNMENT), + LintId::of(&types::CAST_REF_TO_MUT), + LintId::of(&types::CHAR_LIT_AS_U8), + LintId::of(&types::FN_TO_NUMERIC_CAST), + LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), + LintId::of(&types::REDUNDANT_ALLOCATION), + LintId::of(&types::TYPE_COMPLEXITY), + LintId::of(&types::UNIT_ARG), + LintId::of(&types::UNIT_CMP), + LintId::of(&types::UNNECESSARY_CAST), + LintId::of(&types::VEC_BOX), + LintId::of(&unicode::ZERO_WIDTH_SPACE), + LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), + LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), + LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(&unwrap::PANICKING_UNWRAP), + LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&vec::USELESS_VEC), + LintId::of(&write::PRINTLN_EMPTY_STRING), + LintId::of(&write::PRINT_LITERAL), + LintId::of(&write::PRINT_WITH_NEWLINE), + LintId::of(&write::WRITELN_EMPTY_STRING), + LintId::of(&write::WRITE_LITERAL), + LintId::of(&write::WRITE_WITH_NEWLINE), + LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), + ]); + + store.register_group(true, "clippy::style", Some("clippy_style"), vec![ + LintId::of(&assertions_on_constants::ASSERTIONS_ON_CONSTANTS), + LintId::of(&assign_ops::ASSIGN_OP_PATTERN), + LintId::of(&attrs::UNKNOWN_CLIPPY_LINTS), + LintId::of(&bit_mask::VERBOSE_BIT_MASK), + LintId::of(&blacklisted_name::BLACKLISTED_NAME), + LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_EXPR), + LintId::of(&block_in_if_condition::BLOCK_IN_IF_CONDITION_STMT), + LintId::of(&collapsible_if::COLLAPSIBLE_IF), + LintId::of(&comparison_chain::COMPARISON_CHAIN), + LintId::of(&doc::MISSING_SAFETY_DOC), + LintId::of(&doc::NEEDLESS_DOCTEST_MAIN), + LintId::of(&enum_variants::ENUM_VARIANT_NAMES), + LintId::of(&enum_variants::MODULE_INCEPTION), + LintId::of(&eq_op::OP_REF), + LintId::of(&eta_reduction::REDUNDANT_CLOSURE), + LintId::of(&float_literal::EXCESSIVE_PRECISION), + LintId::of(&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING), + LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING), + LintId::of(&functions::DOUBLE_MUST_USE), + LintId::of(&functions::MUST_USE_UNIT), + LintId::of(&if_let_some_result::IF_LET_SOME_RESULT), + LintId::of(&inherent_to_string::INHERENT_TO_STRING), + LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), + LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_if_seq::USELESS_LET_IF_SEQ), + LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), + LintId::of(&loops::EMPTY_LOOP), + LintId::of(&loops::FOR_KV_MAP), + LintId::of(&loops::NEEDLESS_RANGE_LOOP), + LintId::of(&loops::WHILE_LET_ON_ITERATOR), + LintId::of(&main_recursion::MAIN_RECURSION), + LintId::of(&map_clone::MAP_CLONE), + LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), + LintId::of(&matches::MATCH_OVERLAPPING_ARM), + LintId::of(&matches::MATCH_REF_PATS), + LintId::of(&matches::MATCH_WILD_ERR_ARM), + LintId::of(&matches::SINGLE_MATCH), + LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), + LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), + LintId::of(&methods::CHARS_LAST_CMP), + LintId::of(&methods::CHARS_NEXT_CMP), + LintId::of(&methods::INTO_ITER_ON_REF), + LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NTH_ZERO), + LintId::of(&methods::ITER_SKIP_NEXT), + LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), + LintId::of(&methods::NEW_RET_NO_SELF), + LintId::of(&methods::OK_EXPECT), + LintId::of(&methods::OPTION_MAP_OR_NONE), + LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION), + LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT), + LintId::of(&methods::STRING_EXTEND_CHARS), + LintId::of(&methods::UNNECESSARY_FOLD), + LintId::of(&methods::WRONG_SELF_CONVENTION), + LintId::of(&misc::TOPLEVEL_REF_ARG), + LintId::of(&misc::ZERO_PTR), + LintId::of(&misc_early::BUILTIN_TYPE_SHADOW), + LintId::of(&misc_early::DOUBLE_NEG), + LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), + LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), + LintId::of(&misc_early::REDUNDANT_PATTERN), + LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), + LintId::of(&neg_multiply::NEG_MULTIPLY), + LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), + LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), + LintId::of(&non_expressive_names::MANY_SINGLE_CHAR_NAMES), + LintId::of(&panic_unimplemented::PANIC_PARAMS), + LintId::of(&ptr::CMP_NULL), + LintId::of(&ptr::PTR_ARG), + LintId::of(&question_mark::QUESTION_MARK), + LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), + LintId::of(&redundant_pattern_matching::REDUNDANT_PATTERN_MATCHING), + LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), + LintId::of(®ex::REGEX_MACRO), + LintId::of(®ex::TRIVIAL_REGEX), + LintId::of(&returns::LET_AND_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), + LintId::of(&returns::UNUSED_UNIT), + LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), + LintId::of(&strings::STRING_LIT_AS_BYTES), + LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), + LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), + LintId::of(&try_err::TRY_ERR), + LintId::of(&types::FN_TO_NUMERIC_CAST), + LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), + LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), + LintId::of(&write::PRINTLN_EMPTY_STRING), + LintId::of(&write::PRINT_LITERAL), + LintId::of(&write::PRINT_WITH_NEWLINE), + LintId::of(&write::WRITELN_EMPTY_STRING), + LintId::of(&write::WRITE_LITERAL), + LintId::of(&write::WRITE_WITH_NEWLINE), + ]); + + store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![ + LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP), + LintId::of(&attrs::DEPRECATED_CFG_ATTR), + LintId::of(&booleans::NONMINIMAL_BOOL), + LintId::of(&double_comparison::DOUBLE_COMPARISONS), + LintId::of(&double_parens::DOUBLE_PARENS), + LintId::of(&duration_subsec::DURATION_SUBSEC), + LintId::of(&eval_order_dependence::DIVERGING_SUB_EXPRESSION), + LintId::of(&eval_order_dependence::EVAL_ORDER_DEPENDENCE), + LintId::of(&explicit_write::EXPLICIT_WRITE), + LintId::of(&format::USELESS_FORMAT), + LintId::of(&functions::TOO_MANY_ARGUMENTS), + LintId::of(&get_last_with_len::GET_LAST_WITH_LEN), + LintId::of(&identity_conversion::IDENTITY_CONVERSION), + LintId::of(&identity_op::IDENTITY_OP), + LintId::of(&int_plus_one::INT_PLUS_ONE), + LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), + LintId::of(&lifetimes::NEEDLESS_LIFETIMES), + LintId::of(&loops::EXPLICIT_COUNTER_LOOP), + LintId::of(&loops::MUT_RANGE_BOUND), + LintId::of(&loops::WHILE_LET_LOOP), + LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), + LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN), + LintId::of(&matches::MATCH_AS_REF), + LintId::of(&matches::MATCH_SINGLE_BINDING), + LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), + LintId::of(&methods::CLONE_ON_COPY), + LintId::of(&methods::FILTER_NEXT), + LintId::of(&methods::FLAT_MAP_IDENTITY), + LintId::of(&methods::OPTION_AND_THEN_SOME), + LintId::of(&methods::OPTION_AS_REF_DEREF), + LintId::of(&methods::SEARCH_IS_SOME), + LintId::of(&methods::SKIP_WHILE_NEXT), + LintId::of(&methods::SUSPICIOUS_MAP), + LintId::of(&methods::UNNECESSARY_FILTER_MAP), + LintId::of(&methods::USELESS_ASREF), + LintId::of(&misc::SHORT_CIRCUIT_STATEMENT), + LintId::of(&misc_early::REDUNDANT_CLOSURE_CALL), + LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN), + LintId::of(&misc_early::ZERO_PREFIXED_LITERAL), + LintId::of(&needless_bool::BOOL_COMPARISON), + LintId::of(&needless_bool::NEEDLESS_BOOL), + LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), + LintId::of(&needless_update::NEEDLESS_UPDATE), + LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), + LintId::of(&no_effect::NO_EFFECT), + LintId::of(&no_effect::UNNECESSARY_OPERATION), + LintId::of(&overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), + LintId::of(&partialeq_ne_impl::PARTIALEQ_NE_IMPL), + LintId::of(&precedence::PRECEDENCE), + LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), + LintId::of(&ranges::RANGE_MINUS_ONE), + LintId::of(&ranges::RANGE_ZIP_WITH_LEN), + LintId::of(&reference::DEREF_ADDROF), + LintId::of(&reference::REF_IN_DEREF), + LintId::of(&swap::MANUAL_SWAP), + LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), + LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), + LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR), + LintId::of(&transmute::TRANSMUTE_FLOAT_TO_INT), + LintId::of(&transmute::TRANSMUTE_INT_TO_BOOL), + LintId::of(&transmute::TRANSMUTE_INT_TO_CHAR), + LintId::of(&transmute::TRANSMUTE_INT_TO_FLOAT), + LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR), + LintId::of(&transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(&types::BORROWED_BOX), + LintId::of(&types::CHAR_LIT_AS_U8), + LintId::of(&types::TYPE_COMPLEXITY), + LintId::of(&types::UNIT_ARG), + LintId::of(&types::UNNECESSARY_CAST), + LintId::of(&types::VEC_BOX), + LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), + ]); + + store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![ + LintId::of(&approx_const::APPROX_CONSTANT), + LintId::of(&atomic_ordering::INVALID_ATOMIC_ORDERING), + LintId::of(&attrs::DEPRECATED_SEMVER), + LintId::of(&attrs::MISMATCHED_TARGET_OS), + LintId::of(&attrs::USELESS_ATTRIBUTE), + LintId::of(&bit_mask::BAD_BIT_MASK), + LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), + LintId::of(&booleans::LOGIC_BUG), + LintId::of(&copies::IFS_SAME_COND), + LintId::of(&copies::IF_SAME_THEN_ELSE), + LintId::of(&derive::DERIVE_HASH_XOR_EQ), + LintId::of(&drop_bounds::DROP_BOUNDS), + LintId::of(&drop_forget_ref::DROP_COPY), + LintId::of(&drop_forget_ref::DROP_REF), + LintId::of(&drop_forget_ref::FORGET_COPY), + LintId::of(&drop_forget_ref::FORGET_REF), + LintId::of(&enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), + LintId::of(&eq_op::EQ_OP), + LintId::of(&erasing_op::ERASING_OP), + LintId::of(&formatting::POSSIBLE_MISSING_COMMA), + LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF), + LintId::of(&if_let_mutex::IF_LET_MUTEX), + LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING), + LintId::of(&infinite_iter::INFINITE_ITER), + LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY), + LintId::of(&inline_fn_without_body::INLINE_FN_WITHOUT_BODY), + LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), + LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), + LintId::of(&loops::FOR_LOOP_OVER_OPTION), + LintId::of(&loops::FOR_LOOP_OVER_RESULT), + LintId::of(&loops::ITER_NEXT_LOOP), + LintId::of(&loops::NEVER_LOOP), + LintId::of(&loops::REVERSE_RANGE_LOOP), + LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), + LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), + LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), + LintId::of(&mem_replace::MEM_REPLACE_WITH_UNINIT), + LintId::of(&methods::CLONE_DOUBLE_REF), + LintId::of(&methods::ITERATOR_STEP_BY_ZERO), + LintId::of(&methods::TEMPORARY_CSTRING_AS_PTR), + LintId::of(&methods::UNINIT_ASSUMED_INIT), + LintId::of(&methods::ZST_OFFSET), + LintId::of(&minmax::MIN_MAX), + LintId::of(&misc::CMP_NAN), + LintId::of(&misc::FLOAT_CMP), + LintId::of(&misc::MODULO_ONE), + LintId::of(&mut_key::MUTABLE_KEY_TYPE), + LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), + LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST), + LintId::of(&open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(&option_env_unwrap::OPTION_ENV_UNWRAP), + LintId::of(&ptr::MUT_FROM_REF), + LintId::of(®ex::INVALID_REGEX), + LintId::of(&serde_api::SERDE_API_MISUSE), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), + LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), + LintId::of(&swap::ALMOST_SWAPPED), + LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE), + LintId::of(&transmute::WRONG_TRANSMUTE), + LintId::of(&transmuting_null::TRANSMUTING_NULL), + LintId::of(&types::ABSURD_EXTREME_COMPARISONS), + LintId::of(&types::CAST_PTR_ALIGNMENT), + LintId::of(&types::CAST_REF_TO_MUT), + LintId::of(&types::UNIT_CMP), + LintId::of(&unicode::ZERO_WIDTH_SPACE), + LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), + LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(&unwrap::PANICKING_UNWRAP), + ]); + + store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ + LintId::of(&bytecount::NAIVE_BYTECOUNT), + LintId::of(&entry::MAP_ENTRY), + LintId::of(&escape::BOXED_LOCAL), + LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS), + LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), + LintId::of(&loops::MANUAL_MEMCPY), + LintId::of(&loops::NEEDLESS_COLLECT), + LintId::of(&methods::EXPECT_FUN_CALL), + LintId::of(&methods::ITER_NTH), + LintId::of(&methods::OR_FUN_CALL), + LintId::of(&methods::SINGLE_CHAR_PATTERN), + LintId::of(&misc::CMP_OWNED), + LintId::of(&mutex_atomic::MUTEX_ATOMIC), + LintId::of(&redundant_clone::REDUNDANT_CLONE), + LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), + LintId::of(&types::BOX_VEC), + LintId::of(&types::REDUNDANT_ALLOCATION), + LintId::of(&vec::USELESS_VEC), + ]); + + store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![ + LintId::of(&cargo_common_metadata::CARGO_COMMON_METADATA), + LintId::of(&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS), + LintId::of(&wildcard_dependencies::WILDCARD_DEPENDENCIES), + ]); + + store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ + LintId::of(&attrs::EMPTY_LINE_AFTER_OUTER_ATTR), + LintId::of(&cognitive_complexity::COGNITIVE_COMPLEXITY), + LintId::of(&fallible_impl_from::FALLIBLE_IMPL_FROM), + LintId::of(&floating_point_arithmetic::IMPRECISE_FLOPS), + LintId::of(&floating_point_arithmetic::SUBOPTIMAL_FLOPS), + LintId::of(&future_not_send::FUTURE_NOT_SEND), + LintId::of(&missing_const_for_fn::MISSING_CONST_FOR_FN), + LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), + LintId::of(&mutex_atomic::MUTEX_INTEGER), + LintId::of(&needless_borrow::NEEDLESS_BORROW), + LintId::of(&path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), + LintId::of(&redundant_pub_crate::REDUNDANT_PUB_CRATE), + LintId::of(&transmute::USELESS_TRANSMUTE), + LintId::of(&use_self::USE_SELF), + ]); +} + +#[rustfmt::skip] +fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { + store.register_removed( + "should_assert_eq", + "`assert!()` will be more flexible with RFC 2011", + ); + store.register_removed( + "extend_from_slice", + "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice", + ); + store.register_removed( + "range_step_by_zero", + "`iterator.step_by(0)` panics nowadays", + ); + store.register_removed( + "unstable_as_slice", + "`Vec::as_slice` has been stabilized in 1.7", + ); + store.register_removed( + "unstable_as_mut_slice", + "`Vec::as_mut_slice` has been stabilized in 1.7", + ); + store.register_removed( + "str_to_string", + "using `str::to_string` is common even today and specialization will likely happen soon", + ); + store.register_removed( + "string_to_string", + "using `string::to_string` is common even today and specialization will likely happen soon", + ); + store.register_removed( + "misaligned_transmute", + "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr", + ); + store.register_removed( + "assign_ops", + "using compound assignment operators (e.g., `+=`) is harmless", + ); + store.register_removed( + "if_let_redundant_pattern_matching", + "this lint has been changed to redundant_pattern_matching", + ); + store.register_removed( + "unsafe_vector_initialization", + "the replacement suggested by this lint had substantially different behavior", + ); +} + +/// Register renamed lints. +/// +/// Used in `./src/driver.rs`. +pub fn register_renamed(ls: &mut rustc_lint::LintStore) { + ls.register_renamed("clippy::stutter", "clippy::module_name_repetitions"); + ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); + ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); + ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"); +} + +// only exists to let the dogfood integration test works. +// Don't run clippy as an executable directly +#[allow(dead_code)] +fn main() { + panic!("Please use the cargo-clippy executable"); +} diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs new file mode 100644 index 0000000000000..d80ad47ab2468 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -0,0 +1,526 @@ +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{ + walk_fn_decl, walk_generic_param, walk_generics, walk_param_bound, walk_ty, NestedVisitorMap, Visitor, +}; +use rustc_hir::FnRetTy::Return; +use rustc_hir::{ + BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, + ItemKind, Lifetime, LifetimeName, ParamName, QPath, TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty, + TyKind, WhereClause, WherePredicate, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::kw; + +use crate::reexport::Name; +use crate::utils::{in_macro, last_path_segment, span_lint, trait_ref_of_method}; + +declare_clippy_lint! { + /// **What it does:** Checks for lifetime annotations which can be removed by + /// relying on lifetime elision. + /// + /// **Why is this bad?** The additional lifetimes make the code look more + /// complicated, while there is nothing out of the ordinary going on. Removing + /// them leads to more readable code. + /// + /// **Known problems:** Potential false negatives: we bail out if the function + /// has a `where` clause where lifetimes are mentioned. + /// + /// **Example:** + /// ```rust + /// // Bad: unnecessary lifetime annotations + /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { + /// x + /// } + /// + /// // Good + /// fn elided(x: &u8, y: u8) -> &u8 { + /// x + /// } + /// ``` + pub NEEDLESS_LIFETIMES, + complexity, + "using explicit lifetimes for references in function arguments when elision rules \ + would allow omitting them" +} + +declare_clippy_lint! { + /// **What it does:** Checks for lifetimes in generics that are never used + /// anywhere else. + /// + /// **Why is this bad?** The additional lifetimes make the code look more + /// complicated, while there is nothing out of the ordinary going on. Removing + /// them leads to more readable code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad: unnecessary lifetimes + /// fn unused_lifetime<'a>(x: u8) { + /// // .. + /// } + /// + /// // Good + /// fn no_lifetime(x: u8) { + /// // ... + /// } + /// ``` + pub EXTRA_UNUSED_LIFETIMES, + complexity, + "unused lifetimes in function definitions" +} + +declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Lifetimes { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Fn(ref sig, ref generics, id) = item.kind { + check_fn_inner(cx, &sig.decl, Some(id), generics, item.span, true); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(ref sig, id) = item.kind { + let report_extra_lifetimes = trait_ref_of_method(cx, item.hir_id).is_none(); + check_fn_inner( + cx, + &sig.decl, + Some(id), + &item.generics, + item.span, + report_extra_lifetimes, + ); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(ref sig, ref body) = item.kind { + let body = match *body { + TraitFn::Required(_) => None, + TraitFn::Provided(id) => Some(id), + }; + check_fn_inner(cx, &sig.decl, body, &item.generics, item.span, true); + } + } +} + +/// The lifetime of a &-reference. +#[derive(PartialEq, Eq, Hash, Debug)] +enum RefLt { + Unnamed, + Static, + Named(Name), +} + +fn check_fn_inner<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + decl: &'tcx FnDecl<'_>, + body: Option, + generics: &'tcx Generics<'_>, + span: Span, + report_extra_lifetimes: bool, +) { + if in_macro(span) || has_where_lifetimes(cx, &generics.where_clause) { + return; + } + + let mut bounds_lts = Vec::new(); + let types = generics.params.iter().filter(|param| match param.kind { + GenericParamKind::Type { .. } => true, + _ => false, + }); + for typ in types { + for bound in typ.bounds { + let mut visitor = RefVisitor::new(cx); + walk_param_bound(&mut visitor, bound); + if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) { + return; + } + if let GenericBound::Trait(ref trait_ref, _) = *bound { + let params = &trait_ref + .trait_ref + .path + .segments + .last() + .expect("a path must have at least one segment") + .args; + if let Some(ref params) = *params { + let lifetimes = params.args.iter().filter_map(|arg| match arg { + GenericArg::Lifetime(lt) => Some(lt), + _ => None, + }); + for bound in lifetimes { + if bound.name != LifetimeName::Static && !bound.is_elided() { + return; + } + bounds_lts.push(bound); + } + } + } + } + } + if could_use_elision(cx, decl, body, &generics.params, bounds_lts) { + span_lint( + cx, + NEEDLESS_LIFETIMES, + span.with_hi(decl.output.span().hi()), + "explicit lifetimes given in parameter types where they could be elided \ + (or replaced with `'_` if needed by type declaration)", + ); + } + if report_extra_lifetimes { + self::report_extra_lifetimes(cx, decl, generics); + } +} + +fn could_use_elision<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + func: &'tcx FnDecl<'_>, + body: Option, + named_generics: &'tcx [GenericParam<'_>], + bounds_lts: Vec<&'tcx Lifetime>, +) -> bool { + // There are two scenarios where elision works: + // * no output references, all input references have different LT + // * output references, exactly one input reference with same LT + // All lifetimes must be unnamed, 'static or defined without bounds on the + // level of the current item. + + // check named LTs + let allowed_lts = allowed_lts_from(named_generics); + + // these will collect all the lifetimes for references in arg/return types + let mut input_visitor = RefVisitor::new(cx); + let mut output_visitor = RefVisitor::new(cx); + + // extract lifetimes in input argument types + for arg in func.inputs { + input_visitor.visit_ty(arg); + } + // extract lifetimes in output type + if let Return(ref ty) = func.output { + output_visitor.visit_ty(ty); + } + + let input_lts = match input_visitor.into_vec() { + Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()), + None => return false, + }; + let output_lts = match output_visitor.into_vec() { + Some(val) => val, + None => return false, + }; + + if let Some(body_id) = body { + let mut checker = BodyLifetimeChecker { + lifetimes_used_in_body: false, + }; + checker.visit_expr(&cx.tcx.hir().body(body_id).value); + if checker.lifetimes_used_in_body { + return false; + } + } + + // check for lifetimes from higher scopes + for lt in input_lts.iter().chain(output_lts.iter()) { + if !allowed_lts.contains(lt) { + return false; + } + } + + // no input lifetimes? easy case! + if input_lts.is_empty() { + false + } else if output_lts.is_empty() { + // no output lifetimes, check distinctness of input lifetimes + + // only unnamed and static, ok + let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static); + if unnamed_and_static { + return false; + } + // we have no output reference, so we only need all distinct lifetimes + input_lts.len() == unique_lifetimes(&input_lts) + } else { + // we have output references, so we need one input reference, + // and all output lifetimes must be the same + if unique_lifetimes(&output_lts) > 1 { + return false; + } + if input_lts.len() == 1 { + match (&input_lts[0], &output_lts[0]) { + (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true, + (&RefLt::Named(_), &RefLt::Unnamed) => true, + _ => false, /* already elided, different named lifetimes + * or something static going on */ + } + } else { + false + } + } +} + +fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet { + let mut allowed_lts = FxHashSet::default(); + for par in named_generics.iter() { + if let GenericParamKind::Lifetime { .. } = par.kind { + if par.bounds.is_empty() { + allowed_lts.insert(RefLt::Named(par.name.ident().name)); + } + } + } + allowed_lts.insert(RefLt::Unnamed); + allowed_lts.insert(RefLt::Static); + allowed_lts +} + +fn lts_from_bounds<'a, T: Iterator>(mut vec: Vec, bounds_lts: T) -> Vec { + for lt in bounds_lts { + if lt.name != LifetimeName::Static { + vec.push(RefLt::Named(lt.name.ident().name)); + } + } + + vec +} + +/// Number of unique lifetimes in the given vector. +#[must_use] +fn unique_lifetimes(lts: &[RefLt]) -> usize { + lts.iter().collect::>().len() +} + +/// A visitor usable for `rustc_front::visit::walk_ty()`. +struct RefVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + lts: Vec, + abort: bool, +} + +impl<'v, 't> RefVisitor<'v, 't> { + fn new(cx: &'v LateContext<'v, 't>) -> Self { + Self { + cx, + lts: Vec::new(), + abort: false, + } + } + + fn record(&mut self, lifetime: &Option) { + if let Some(ref lt) = *lifetime { + if lt.name == LifetimeName::Static { + self.lts.push(RefLt::Static); + } else if let LifetimeName::Param(ParamName::Fresh(_)) = lt.name { + // Fresh lifetimes generated should be ignored. + } else if lt.is_elided() { + self.lts.push(RefLt::Unnamed); + } else { + self.lts.push(RefLt::Named(lt.name.ident().name)); + } + } else { + self.lts.push(RefLt::Unnamed); + } + } + + fn into_vec(self) -> Option> { + if self.abort { + None + } else { + Some(self.lts) + } + } + + fn collect_anonymous_lifetimes(&mut self, qpath: &QPath<'_>, ty: &Ty<'_>) { + if let Some(ref last_path_segment) = last_path_segment(qpath).args { + if !last_path_segment.parenthesized + && !last_path_segment.args.iter().any(|arg| match arg { + GenericArg::Lifetime(_) => true, + _ => false, + }) + { + let hir_id = ty.hir_id; + match self.cx.tables.qpath_res(qpath, hir_id) { + Res::Def(DefKind::TyAlias | DefKind::Struct, def_id) => { + let generics = self.cx.tcx.generics_of(def_id); + for _ in generics.params.as_slice() { + self.record(&None); + } + }, + Res::Def(DefKind::Trait, def_id) => { + let trait_def = self.cx.tcx.trait_def(def_id); + for _ in &self.cx.tcx.generics_of(trait_def.def_id).params { + self.record(&None); + } + }, + _ => (), + } + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + // for lifetimes as parameters of generics + fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { + self.record(&Some(*lifetime)); + } + + fn visit_ty(&mut self, ty: &'tcx Ty<'_>) { + match ty.kind { + TyKind::Rptr(ref lt, _) if lt.is_elided() => { + self.record(&None); + }, + TyKind::Path(ref path) => { + self.collect_anonymous_lifetimes(path, ty); + }, + TyKind::Def(item, _) => { + let map = self.cx.tcx.hir(); + if let ItemKind::OpaqueTy(ref exist_ty) = map.expect_item(item.id).kind { + for bound in exist_ty.bounds { + if let GenericBound::Outlives(_) = *bound { + self.record(&None); + } + } + } else { + unreachable!() + } + walk_ty(self, ty); + }, + TyKind::TraitObject(bounds, ref lt) => { + if !lt.is_elided() { + self.abort = true; + } + for bound in bounds { + self.visit_poly_trait_ref(bound, TraitBoundModifier::None); + } + return; + }, + _ => (), + } + walk_ty(self, ty); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Are any lifetimes mentioned in the `where` clause? If so, we don't try to +/// reason about elision. +fn has_where_lifetimes<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, where_clause: &'tcx WhereClause<'_>) -> bool { + for predicate in where_clause.predicates { + match *predicate { + WherePredicate::RegionPredicate(..) => return true, + WherePredicate::BoundPredicate(ref pred) => { + // a predicate like F: Trait or F: for<'a> Trait<'a> + let mut visitor = RefVisitor::new(cx); + // walk the type F, it may not contain LT refs + walk_ty(&mut visitor, &pred.bounded_ty); + if !visitor.lts.is_empty() { + return true; + } + // if the bounds define new lifetimes, they are fine to occur + let allowed_lts = allowed_lts_from(&pred.bound_generic_params); + // now walk the bounds + for bound in pred.bounds.iter() { + walk_param_bound(&mut visitor, bound); + } + // and check that all lifetimes are allowed + match visitor.into_vec() { + None => return false, + Some(lts) => { + for lt in lts { + if !allowed_lts.contains(<) { + return true; + } + } + }, + } + }, + WherePredicate::EqPredicate(ref pred) => { + let mut visitor = RefVisitor::new(cx); + walk_ty(&mut visitor, &pred.lhs_ty); + walk_ty(&mut visitor, &pred.rhs_ty); + if !visitor.lts.is_empty() { + return true; + } + }, + } + } + false +} + +struct LifetimeChecker { + map: FxHashMap, +} + +impl<'tcx> Visitor<'tcx> for LifetimeChecker { + type Map = Map<'tcx>; + + // for lifetimes as parameters of generics + fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { + self.map.remove(&lifetime.name.ident().name); + } + + fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) { + // don't actually visit `<'a>` or `<'a: 'b>` + // we've already visited the `'a` declarations and + // don't want to spuriously remove them + // `'b` in `'a: 'b` is useless unless used elsewhere in + // a non-lifetime bound + if let GenericParamKind::Type { .. } = param.kind { + walk_generic_param(self, param) + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn report_extra_lifetimes<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) { + let hs = generics + .params + .iter() + .filter_map(|par| match par.kind { + GenericParamKind::Lifetime { .. } => Some((par.name.ident().name, par.span)), + _ => None, + }) + .collect(); + let mut checker = LifetimeChecker { map: hs }; + + walk_generics(&mut checker, generics); + walk_fn_decl(&mut checker, func); + + for &v in checker.map.values() { + span_lint( + cx, + EXTRA_UNUSED_LIFETIMES, + v, + "this lifetime isn't used in the function definition", + ); + } +} + +struct BodyLifetimeChecker { + lifetimes_used_in_body: bool, +} + +impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker { + type Map = Map<'tcx>; + + // for lifetimes as parameters of generics + fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { + if lifetime.name.ident().name != kw::Invalid && lifetime.name.ident().name != kw::StaticLifetime { + self.lifetimes_used_in_body = true; + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/literal_representation.rs b/src/tools/clippy/clippy_lints/src/literal_representation.rs new file mode 100644 index 0000000000000..ec7c4531ed716 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/literal_representation.rs @@ -0,0 +1,430 @@ +//! Lints concerned with the grouping of digits with underscores in integral or +//! floating-point literal expressions. + +use crate::utils::{ + in_macro, + numeric_literal::{NumericLiteral, Radix}, + snippet_opt, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Warns if a long integral or floating-point constant does + /// not contain underscores. + /// + /// **Why is this bad?** Reading long numbers is difficult without separators. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x: u64 = 61864918973511; + /// ``` + pub UNREADABLE_LITERAL, + pedantic, + "long integer literal without underscores" +} + +declare_clippy_lint! { + /// **What it does:** Warns for mistyped suffix in literals + /// + /// **Why is this bad?** This is most probably a typo + /// + /// **Known problems:** + /// - Recommends a signed suffix, even though the number might be too big and an unsigned + /// suffix is required + /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers + /// + /// **Example:** + /// + /// ```rust + /// 2_32; + /// ``` + pub MISTYPED_LITERAL_SUFFIXES, + correctness, + "mistyped literal suffix" +} + +declare_clippy_lint! { + /// **What it does:** Warns if an integral or floating-point constant is + /// grouped inconsistently with underscores. + /// + /// **Why is this bad?** Readers may incorrectly interpret inconsistently + /// grouped digits. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x: u64 = 618_64_9189_73_511; + /// ``` + pub INCONSISTENT_DIGIT_GROUPING, + style, + "integer literals with digits grouped inconsistently" +} + +declare_clippy_lint! { + /// **What it does:** Warns if the digits of an integral or floating-point + /// constant are grouped into groups that + /// are too large. + /// + /// **Why is this bad?** Negatively impacts readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x: u64 = 6186491_8973511; + /// ``` + pub LARGE_DIGIT_GROUPS, + pedantic, + "grouping digits into groups that are too large" +} + +declare_clippy_lint! { + /// **What it does:** Warns if there is a better representation for a numeric literal. + /// + /// **Why is this bad?** Especially for big powers of 2 a hexadecimal representation is more + /// readable than a decimal representation. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// `255` => `0xFF` + /// `65_535` => `0xFFFF` + /// `4_042_322_160` => `0xF0F0_F0F0` + pub DECIMAL_LITERAL_REPRESENTATION, + restriction, + "using decimal representation when hexadecimal would be better" +} + +enum WarningType { + UnreadableLiteral, + InconsistentDigitGrouping, + LargeDigitGroups, + DecimalRepresentation, + MistypedLiteralSuffix, +} + +impl WarningType { + fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) { + match self { + Self::MistypedLiteralSuffix => span_lint_and_sugg( + cx, + MISTYPED_LITERAL_SUFFIXES, + span, + "mistyped literal suffix", + "did you mean to write", + suggested_format, + Applicability::MaybeIncorrect, + ), + Self::UnreadableLiteral => span_lint_and_sugg( + cx, + UNREADABLE_LITERAL, + span, + "long literal lacking separators", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::LargeDigitGroups => span_lint_and_sugg( + cx, + LARGE_DIGIT_GROUPS, + span, + "digit groups should be smaller", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::InconsistentDigitGrouping => span_lint_and_sugg( + cx, + INCONSISTENT_DIGIT_GROUPING, + span, + "digits grouped inconsistently by underscores", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + Self::DecimalRepresentation => span_lint_and_sugg( + cx, + DECIMAL_LITERAL_REPRESENTATION, + span, + "integer literal has a better hexadecimal representation", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), + }; + } +} + +declare_lint_pass!(LiteralDigitGrouping => [ + UNREADABLE_LITERAL, + INCONSISTENT_DIGIT_GROUPING, + LARGE_DIGIT_GROUPS, + MISTYPED_LITERAL_SUFFIXES, +]); + +impl EarlyLintPass for LiteralDigitGrouping { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Lit(ref lit) = expr.kind { + Self::check_lit(cx, lit) + } + } +} + +// Length of each UUID hyphenated group in hex digits. +const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; + +impl LiteralDigitGrouping { + fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) { + if_chain! { + if let Some(src) = snippet_opt(cx, lit.span); + if let Some(mut num_lit) = NumericLiteral::from_lit(&src, &lit); + then { + if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) { + return; + } + + if Self::is_literal_uuid_formatted(&mut num_lit) { + return; + } + + let result = (|| { + + let integral_group_size = Self::get_group_size(num_lit.integer.split('_'))?; + if let Some(fraction) = num_lit.fraction { + let fractional_group_size = Self::get_group_size(fraction.rsplit('_'))?; + + let consistent = Self::parts_consistent(integral_group_size, + fractional_group_size, + num_lit.integer.len(), + fraction.len()); + if !consistent { + return Err(WarningType::InconsistentDigitGrouping); + }; + } + Ok(()) + })(); + + + if let Err(warning_type) = result { + let should_warn = match warning_type { + | WarningType::UnreadableLiteral + | WarningType::InconsistentDigitGrouping + | WarningType::LargeDigitGroups => { + !in_macro(lit.span) + } + WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => { + true + } + }; + if should_warn { + warning_type.display(num_lit.format(), cx, lit.span) + } + } + } + } + } + + // Returns `false` if the check fails + fn check_for_mistyped_suffix( + cx: &EarlyContext<'_>, + span: rustc_span::Span, + num_lit: &mut NumericLiteral<'_>, + ) -> bool { + if num_lit.suffix.is_some() { + return true; + } + + let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent { + (exponent, &["32", "64"][..], 'f') + } else if let Some(fraction) = &mut num_lit.fraction { + (fraction, &["32", "64"][..], 'f') + } else { + (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i') + }; + + let mut split = part.rsplit('_'); + let last_group = split.next().expect("At least one group"); + if split.next().is_some() && mistyped_suffixes.contains(&last_group) { + *part = &part[..part.len() - last_group.len()]; + let mut sugg = num_lit.format(); + sugg.push('_'); + sugg.push(missing_char); + sugg.push_str(last_group); + WarningType::MistypedLiteralSuffix.display(sugg, cx, span); + false + } else { + true + } + } + + /// Checks whether the numeric literal matches the formatting of a UUID. + /// + /// Returns `true` if the radix is hexadecimal, and the groups match the + /// UUID format of 8-4-4-4-12. + fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool { + if num_lit.radix != Radix::Hexadecimal { + return false; + } + + // UUIDs should not have a fraction + if num_lit.fraction.is_some() { + return false; + } + + let group_sizes: Vec = num_lit.integer.split('_').map(str::len).collect(); + if UUID_GROUP_LENS.len() == group_sizes.len() { + UUID_GROUP_LENS.iter().zip(&group_sizes).all(|(&a, &b)| a == b) + } else { + false + } + } + + /// Given the sizes of the digit groups of both integral and fractional + /// parts, and the length + /// of both parts, determine if the digits have been grouped consistently. + #[must_use] + fn parts_consistent( + int_group_size: Option, + frac_group_size: Option, + int_size: usize, + frac_size: usize, + ) -> bool { + match (int_group_size, frac_group_size) { + // No groups on either side of decimal point - trivially consistent. + (None, None) => true, + // Integral part has grouped digits, fractional part does not. + (Some(int_group_size), None) => frac_size <= int_group_size, + // Fractional part has grouped digits, integral part does not. + (None, Some(frac_group_size)) => int_size <= frac_group_size, + // Both parts have grouped digits. Groups should be the same size. + (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size, + } + } + + /// Returns the size of the digit groups (or None if ungrouped) if successful, + /// otherwise returns a `WarningType` for linting. + fn get_group_size<'a>(groups: impl Iterator) -> Result, WarningType> { + let mut groups = groups.map(str::len); + + let first = groups.next().expect("At least one group"); + + if let Some(second) = groups.next() { + if !groups.all(|x| x == second) || first > second { + Err(WarningType::InconsistentDigitGrouping) + } else if second > 4 { + Err(WarningType::LargeDigitGroups) + } else { + Ok(Some(second)) + } + } else if first > 5 { + Err(WarningType::UnreadableLiteral) + } else { + Ok(None) + } + } +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Copy, Clone)] +pub struct DecimalLiteralRepresentation { + threshold: u64, +} + +impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]); + +impl EarlyLintPass for DecimalLiteralRepresentation { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Lit(ref lit) = expr.kind { + self.check_lit(cx, lit) + } + } +} + +impl DecimalLiteralRepresentation { + #[must_use] + pub fn new(threshold: u64) -> Self { + Self { threshold } + } + fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) { + // Lint integral literals. + if_chain! { + if let LitKind::Int(val, _) = lit.kind; + if let Some(src) = snippet_opt(cx, lit.span); + if let Some(num_lit) = NumericLiteral::from_lit(&src, &lit); + if num_lit.radix == Radix::Decimal; + if val >= u128::from(self.threshold); + then { + let hex = format!("{:#X}", val); + let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); + let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| { + warning_type.display(num_lit.format(), cx, lit.span) + }); + } + } + } + + fn do_lint(digits: &str) -> Result<(), WarningType> { + if digits.len() == 1 { + // Lint for 1 digit literals, if someone really sets the threshold that low + if digits == "1" + || digits == "2" + || digits == "4" + || digits == "8" + || digits == "3" + || digits == "7" + || digits == "F" + { + return Err(WarningType::DecimalRepresentation); + } + } else if digits.len() < 4 { + // Lint for Literals with a hex-representation of 2 or 3 digits + let f = &digits[0..1]; // first digit + let s = &digits[1..]; // suffix + + // Powers of 2 + if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0')) + // Powers of 2 minus 1 + || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F')) + { + return Err(WarningType::DecimalRepresentation); + } + } else { + // Lint for Literals with a hex-representation of 4 digits or more + let f = &digits[0..1]; // first digit + let m = &digits[1..digits.len() - 1]; // middle digits, except last + let s = &digits[1..]; // suffix + + // Powers of 2 with a margin of +15/-16 + if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0')) + || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F')) + // Lint for representations with only 0s and Fs, while allowing 7 as the first + // digit + || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F')) + { + return Err(WarningType::DecimalRepresentation); + } + } + + Ok(()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops.rs b/src/tools/clippy/clippy_lints/src/loops.rs new file mode 100644 index 0000000000000..f7c7fd82d833b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops.rs @@ -0,0 +1,2550 @@ +use crate::consts::{constant, Constant}; +use crate::reexport::Name; +use crate::utils::paths; +use crate::utils::usage::{is_unused, mutated_variables}; +use crate::utils::{ + get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, + is_integer_const, is_no_std_crate, is_refutable, last_path_segment, match_trait_method, match_type, match_var, + multispan_sugg, snippet, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help, + span_lint_and_sugg, span_lint_and_then, SpanlessEq, +}; +use crate::utils::{is_type_diagnostic_item, qpath_res, same_tys, sext, sugg}; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_ast::ast; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{ + def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, LoopSource, + MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, +}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::middle::region; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::BytePos; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase}; +use std::iter::{once, Iterator}; +use std::mem; + +declare_clippy_lint! { + /// **What it does:** Checks for for-loops that manually copy items between + /// slices that could be optimized by having a memcpy. + /// + /// **Why is this bad?** It is not as fast as a memcpy. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let src = vec![1]; + /// # let mut dst = vec![0; 65]; + /// for i in 0..src.len() { + /// dst[i + 64] = src[i]; + /// } + /// ``` + /// Could be written as: + /// ```rust + /// # let src = vec![1]; + /// # let mut dst = vec![0; 65]; + /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]); + /// ``` + pub MANUAL_MEMCPY, + perf, + "manually copying items between slices" +} + +declare_clippy_lint! { + /// **What it does:** Checks for looping over the range of `0..len` of some + /// collection just to get the values by index. + /// + /// **Why is this bad?** Just iterating the collection itself makes the intent + /// more clear and is probably faster. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let vec = vec!['a', 'b', 'c']; + /// for i in 0..vec.len() { + /// println!("{}", vec[i]); + /// } + /// ``` + /// Could be written as: + /// ```rust + /// let vec = vec!['a', 'b', 'c']; + /// for i in vec { + /// println!("{}", i); + /// } + /// ``` + pub NEEDLESS_RANGE_LOOP, + style, + "for-looping over a range of indices where an iterator over items would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `x.iter()` where `&x` will do, and + /// suggests the latter. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** False negatives. We currently only warn on some known + /// types. + /// + /// **Example:** + /// ```rust + /// // with `y` a `Vec` or slice: + /// # let y = vec![1]; + /// for x in y.iter() { + /// // .. + /// } + /// ``` + /// can be rewritten to + /// ```rust + /// # let y = vec![1]; + /// for x in &y { + /// // .. + /// } + /// ``` + pub EXPLICIT_ITER_LOOP, + pedantic, + "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `y.into_iter()` where `y` will do, and + /// suggests the latter. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let y = vec![1]; + /// // with `y` a `Vec` or slice: + /// for x in y.into_iter() { + /// // .. + /// } + /// ``` + /// can be rewritten to + /// ```rust + /// # let y = vec![1]; + /// for x in y { + /// // .. + /// } + /// ``` + pub EXPLICIT_INTO_ITER_LOOP, + pedantic, + "for-looping over `_.into_iter()` when `_` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `x.next()`. + /// + /// **Why is this bad?** `next()` returns either `Some(value)` if there was a + /// value, or `None` otherwise. The insidious thing is that `Option<_>` + /// implements `IntoIterator`, so that possibly one value will be iterated, + /// leading to some hard to find bugs. No one will want to write such code + /// [except to win an Underhanded Rust + /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for x in y.next() { + /// .. + /// } + /// ``` + pub ITER_NEXT_LOOP, + correctness, + "for-looping over `_.next()` which is probably not intended" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `for` loops over `Option` values. + /// + /// **Why is this bad?** Readability. This is more clearly expressed as an `if + /// let`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for x in option { + /// .. + /// } + /// ``` + /// + /// This should be + /// ```ignore + /// if let Some(x) = option { + /// .. + /// } + /// ``` + pub FOR_LOOP_OVER_OPTION, + correctness, + "for-looping over an `Option`, which is more clearly expressed as an `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `for` loops over `Result` values. + /// + /// **Why is this bad?** Readability. This is more clearly expressed as an `if + /// let`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for x in result { + /// .. + /// } + /// ``` + /// + /// This should be + /// ```ignore + /// if let Ok(x) = result { + /// .. + /// } + /// ``` + pub FOR_LOOP_OVER_RESULT, + correctness, + "for-looping over a `Result`, which is more clearly expressed as an `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Detects `loop + match` combinations that are easier + /// written as a `while let` loop. + /// + /// **Why is this bad?** The `while let` loop is usually shorter and more + /// readable. + /// + /// **Known problems:** Sometimes the wrong binding is displayed (#383). + /// + /// **Example:** + /// ```rust,no_run + /// # let y = Some(1); + /// loop { + /// let x = match y { + /// Some(x) => x, + /// None => break, + /// }; + /// // .. do something with x + /// } + /// // is easier written as + /// while let Some(x) = y { + /// // .. do something with x + /// }; + /// ``` + pub WHILE_LET_LOOP, + complexity, + "`loop { if let { ... } else break }`, which can be written as a `while let` loop" +} + +declare_clippy_lint! { + /// **What it does:** Checks for functions collecting an iterator when collect + /// is not needed. + /// + /// **Why is this bad?** `collect` causes the allocation of a new data structure, + /// when this allocation may not be needed. + /// + /// **Known problems:** + /// None + /// + /// **Example:** + /// ```rust + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.clone().collect::>().len(); + /// // should be + /// let len = iterator.count(); + /// ``` + pub NEEDLESS_COLLECT, + perf, + "collecting an iterator when collect is not needed" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops over ranges `x..y` where both `x` and `y` + /// are constant and `x` is greater or equal to `y`, unless the range is + /// reversed or has a negative `.step_by(_)`. + /// + /// **Why is it bad?** Such loops will either be skipped or loop until + /// wrap-around (in debug code, this may `panic!()`). Both options are probably + /// not intended. + /// + /// **Known problems:** The lint cannot catch loops over dynamically defined + /// ranges. Doing this would require simulating all possible inputs and code + /// paths through the program, which would be complex and error-prone. + /// + /// **Example:** + /// ```ignore + /// for x in 5..10 - 5 { + /// .. + /// } // oops, stray `-` + /// ``` + pub REVERSE_RANGE_LOOP, + correctness, + "iteration over an empty range, such as `10..0` or `5..5`" +} + +declare_clippy_lint! { + /// **What it does:** Checks `for` loops over slices with an explicit counter + /// and suggests the use of `.enumerate()`. + /// + /// **Why is it bad?** Using `.enumerate()` makes the intent more clear, + /// declutters the code and may be faster in some instances. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v = vec![1]; + /// # fn bar(bar: usize, baz: usize) {} + /// let mut i = 0; + /// for item in &v { + /// bar(i, *item); + /// i += 1; + /// } + /// ``` + /// Could be written as + /// ```rust + /// # let v = vec![1]; + /// # fn bar(bar: usize, baz: usize) {} + /// for (i, item) in v.iter().enumerate() { bar(i, *item); } + /// ``` + pub EXPLICIT_COUNTER_LOOP, + complexity, + "for-looping with an explicit counter when `_.enumerate()` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for empty `loop` expressions. + /// + /// **Why is this bad?** Those busy loops burn CPU cycles without doing + /// anything. Think of the environment and either block on something or at least + /// make the thread sleep for some microseconds. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// loop {} + /// ``` + pub EMPTY_LOOP, + style, + "empty `loop {}`, which should block or sleep" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `while let` expressions on iterators. + /// + /// **Why is this bad?** Readability. A simple `for` loop is shorter and conveys + /// the intent better. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// while let Some(val) = iter() { + /// .. + /// } + /// ``` + pub WHILE_LET_ON_ITERATOR, + style, + "using a while-let loop instead of a for loop on an iterator" +} + +declare_clippy_lint! { + /// **What it does:** Checks for iterating a map (`HashMap` or `BTreeMap`) and + /// ignoring either the keys or values. + /// + /// **Why is this bad?** Readability. There are `keys` and `values` methods that + /// can be used to express that don't need the values or keys. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for (k, _) in &map { + /// .. + /// } + /// ``` + /// + /// could be replaced by + /// + /// ```ignore + /// for k in map.keys() { + /// .. + /// } + /// ``` + pub FOR_KV_MAP, + style, + "looping on a map using `iter` when `keys` or `values` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops that will always `break`, `return` or + /// `continue` an outer loop. + /// + /// **Why is this bad?** This loop never loops, all it does is obfuscating the + /// code. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// loop { + /// ..; + /// break; + /// } + /// ``` + pub NEVER_LOOP, + correctness, + "any loop that will always `break` or `return`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops which have a range bound that is a mutable variable + /// + /// **Why is this bad?** One might think that modifying the mutable variable changes the loop bounds + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let mut foo = 42; + /// for i in 0..foo { + /// foo -= 1; + /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 + /// } + /// ``` + pub MUT_RANGE_BOUND, + complexity, + "for loop over a range where one of the bounds is a mutable variable" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether variables used within while loop condition + /// can be (and are) mutated in the body. + /// + /// **Why is this bad?** If the condition is unchanged, entering the body of the loop + /// will lead to an infinite loop. + /// + /// **Known problems:** If the `while`-loop is in a closure, the check for mutation of the + /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is + /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger. + /// + /// **Example:** + /// ```rust + /// let i = 0; + /// while i > 10 { + /// println!("let me loop forever!"); + /// } + /// ``` + pub WHILE_IMMUTABLE_CONDITION, + correctness, + "variables used within while expression are not mutated in the body" +} + +declare_lint_pass!(Loops => [ + MANUAL_MEMCPY, + NEEDLESS_RANGE_LOOP, + EXPLICIT_ITER_LOOP, + EXPLICIT_INTO_ITER_LOOP, + ITER_NEXT_LOOP, + FOR_LOOP_OVER_RESULT, + FOR_LOOP_OVER_OPTION, + WHILE_LET_LOOP, + NEEDLESS_COLLECT, + REVERSE_RANGE_LOOP, + EXPLICIT_COUNTER_LOOP, + EMPTY_LOOP, + WHILE_LET_ON_ITERATOR, + FOR_KV_MAP, + NEVER_LOOP, + MUT_RANGE_BOUND, + WHILE_IMMUTABLE_CONDITION, +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Loops { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let Some((pat, arg, body)) = higher::for_loop(expr) { + // we don't want to check expanded macros + // this check is not at the top of the function + // since higher::for_loop expressions are marked as expansions + if body.span.from_expansion() { + return; + } + check_for_loop(cx, pat, arg, body, expr); + } + + // we don't want to check expanded macros + if expr.span.from_expansion() { + return; + } + + // check for never_loop + if let ExprKind::Loop(ref block, _, _) = expr.kind { + match never_loop_block(block, expr.hir_id) { + NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), + NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), + } + } + + // check for `loop { if let {} else break }` that could be `while let` + // (also matches an explicit "match" instead of "if let") + // (even if the "match" or "if let" is used for declaration) + if let ExprKind::Loop(ref block, _, LoopSource::Loop) = expr.kind { + // also check for empty `loop {}` statements + if block.stmts.is_empty() && block.expr.is_none() && !is_no_std_crate(cx.tcx.hir().krate()) { + span_lint( + cx, + EMPTY_LOOP, + expr.span, + "empty `loop {}` detected. You may want to either use `panic!()` or add \ + `std::thread::sleep(..);` to the loop body.", + ); + } + + // extract the expression from the first statement (if any) in a block + let inner_stmt_expr = extract_expr_from_first_stmt(block); + // or extract the first expression (if any) from the block + if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(block)) { + if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { + // ensure "if let" compatible match structure + match *source { + MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { + if arms.len() == 2 + && arms[0].guard.is_none() + && arms[1].guard.is_none() + && is_simple_break_expr(&arms[1].body) + { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + // NOTE: we used to build a body here instead of using + // ellipsis, this was removed because: + // 1) it was ugly with big bodies; + // 2) it was not indented properly; + // 3) it wasn’t very smart (see #675). + let mut applicability = Applicability::HasPlaceholders; + span_lint_and_sugg( + cx, + WHILE_LET_LOOP, + expr.span, + "this loop could be written as a `while let` loop", + "try", + format!( + "while let {} = {} {{ .. }}", + snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), + snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability), + ), + applicability, + ); + } + }, + _ => (), + } + } + } + } + if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { + let pat = &arms[0].pat.kind; + if let ( + &PatKind::TupleStruct(ref qpath, ref pat_args, _), + &ExprKind::MethodCall(ref method_path, _, ref method_args), + ) = (pat, &match_expr.kind) + { + let iter_expr = &method_args[0]; + + // Don't lint when the iterator is recreated on every iteration + if_chain! { + if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; + if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR); + if implements_trait(cx, cx.tables.expr_ty(iter_expr), iter_def_id, &[]); + then { + return; + } + } + + let lhs_constructor = last_path_segment(qpath); + if method_path.ident.name == sym!(next) + && match_trait_method(cx, match_expr, &paths::ITERATOR) + && lhs_constructor.ident.name == sym!(Some) + && (pat_args.is_empty() + || !is_refutable(cx, &pat_args[0]) + && !is_used_inside(cx, iter_expr, &arms[0].body) + && !is_iterator_used_after_while_let(cx, iter_expr) + && !is_nested(cx, expr, &method_args[0])) + { + let mut applicability = Applicability::MachineApplicable; + let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); + let loop_var = if pat_args.is_empty() { + "_".to_string() + } else { + snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() + }; + span_lint_and_sugg( + cx, + WHILE_LET_ON_ITERATOR, + expr.span.with_hi(match_expr.span.hi()), + "this loop could be written as a `for` loop", + "try", + format!("for {} in {}", loop_var, iterator), + applicability, + ); + } + } + } + + if let Some((cond, body)) = higher::while_loop(&expr) { + check_infinite_loop(cx, cond, body); + } + + check_needless_collect(expr, cx); + } +} + +enum NeverLoopResult { + // A break/return always get triggered but not necessarily for the main loop. + AlwaysBreak, + // A continue may occur for the main loop. + MayContinueMainLoop, + Otherwise, +} + +#[must_use] +fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { + match *arg { + NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, + NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, + } +} + +// Combine two results for parts that are called in order. +#[must_use] +fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { + match first { + NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, + NeverLoopResult::Otherwise => second, + } +} + +// Combine two results where both parts are called but not necessarily in order. +#[must_use] +fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { + match (left, right) { + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +// Combine two results where only one of the part may have been executed. +#[must_use] +fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { + match (b1, b2) { + (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult { + let stmts = block.stmts.iter().map(stmt_to_expr); + let expr = once(block.expr.as_deref()); + let mut iter = stmts.chain(expr).filter_map(|e| e); + never_loop_expr_seq(&mut iter, main_loop_id) +} + +fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match stmt.kind { + StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), + StmtKind::Local(ref local) => local.init.as_deref(), + _ => None, + } +} + +fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { + match expr.kind { + ExprKind::Box(ref e) + | ExprKind::Unary(_, ref e) + | ExprKind::Cast(ref e, _) + | ExprKind::Type(ref e, _) + | ExprKind::Field(ref e, _) + | ExprKind::AddrOf(_, _, ref e) + | ExprKind::Struct(_, _, Some(ref e)) + | ExprKind::Repeat(ref e, _) + | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), + ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es) | ExprKind::Tup(ref es) => { + never_loop_expr_all(&mut es.iter(), main_loop_id) + }, + ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), + ExprKind::Binary(_, ref e1, ref e2) + | ExprKind::Assign(ref e1, ref e2, _) + | ExprKind::AssignOp(_, ref e1, ref e2) + | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), + ExprKind::Loop(ref b, _, _) => { + // Break can come from the inner loop so remove them. + absorb_break(&never_loop_block(b, main_loop_id)) + }, + ExprKind::Match(ref e, ref arms, _) => { + let e = never_loop_expr(e, main_loop_id); + if arms.is_empty() { + e + } else { + let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id); + combine_seq(e, arms) + } + }, + ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), + ExprKind::Continue(d) => { + let id = d + .target_id + .expect("target ID can only be missing in the presence of compilation errors"); + if id == main_loop_id { + NeverLoopResult::MayContinueMainLoop + } else { + NeverLoopResult::AlwaysBreak + } + }, + ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => { + if let Some(ref e) = *e { + combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) + } else { + NeverLoopResult::AlwaysBreak + } + }, + ExprKind::Struct(_, _, None) + | ExprKind::Yield(_, _) + | ExprKind::Closure(_, _, _, _, _) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Path(_) + | ExprKind::Lit(_) + | ExprKind::Err => NeverLoopResult::Otherwise, + } +} + +fn never_loop_expr_seq<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_seq) +} + +fn never_loop_expr_all<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_both) +} + +fn never_loop_expr_branch<'a, T: Iterator>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult { + e.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::AlwaysBreak, combine_branches) +} + +fn check_for_loop<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + check_for_loop_range(cx, pat, arg, body, expr); + check_for_loop_reverse_range(cx, arg, expr); + check_for_loop_arg(cx, pat, arg, expr); + check_for_loop_explicit_counter(cx, pat, arg, body, expr); + check_for_loop_over_map_kv(cx, pat, arg, body, expr); + check_for_mut_range_bound(cx, arg, body); + detect_manual_memcpy(cx, pat, arg, body, expr); +} + +fn same_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId) -> bool { + if_chain! { + if let ExprKind::Path(ref qpath) = expr.kind; + if let QPath::Resolved(None, ref path) = *qpath; + if path.segments.len() == 1; + if let Res::Local(local_id) = qpath_res(cx, qpath, expr.hir_id); + // our variable! + if local_id == var; + then { + return true; + } + } + + false +} + +struct Offset { + value: String, + negate: bool, +} + +impl Offset { + fn negative(s: String) -> Self { + Self { value: s, negate: true } + } + + fn positive(s: String) -> Self { + Self { + value: s, + negate: false, + } + } +} + +struct FixedOffsetVar { + var_name: String, + offset: Offset, +} + +fn is_slice_like<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'_>) -> bool { + let is_slice = match ty.kind { + ty::Ref(_, subty, _) => is_slice_like(cx, subty), + ty::Slice(..) | ty::Array(..) => true, + _ => false, + }; + + is_slice || is_type_diagnostic_item(cx, ty, sym!(vec_type)) || is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) +} + +fn get_fixed_offset_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId) -> Option { + fn extract_offset<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, e: &Expr<'_>, var: HirId) -> Option { + match e.kind { + ExprKind::Lit(ref l) => match l.node { + ast::LitKind::Int(x, _ty) => Some(x.to_string()), + _ => None, + }, + ExprKind::Path(..) if !same_var(cx, e, var) => Some(snippet_opt(cx, e.span).unwrap_or_else(|| "??".into())), + _ => None, + } + } + + if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind { + let ty = cx.tables.expr_ty(seqexpr); + if !is_slice_like(cx, ty) { + return None; + } + + let offset = match idx.kind { + ExprKind::Binary(op, ref lhs, ref rhs) => match op.node { + BinOpKind::Add => { + let offset_opt = if same_var(cx, lhs, var) { + extract_offset(cx, rhs, var) + } else if same_var(cx, rhs, var) { + extract_offset(cx, lhs, var) + } else { + None + }; + + offset_opt.map(Offset::positive) + }, + BinOpKind::Sub if same_var(cx, lhs, var) => extract_offset(cx, rhs, var).map(Offset::negative), + _ => None, + }, + ExprKind::Path(..) => { + if same_var(cx, idx, var) { + Some(Offset::positive("0".into())) + } else { + None + } + }, + _ => None, + }; + + offset.map(|o| FixedOffsetVar { + var_name: snippet_opt(cx, seqexpr.span).unwrap_or_else(|| "???".into()), + offset: o, + }) + } else { + None + } +} + +fn fetch_cloned_fixed_offset_var<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &Expr<'_>, + var: HirId, +) -> Option { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref args) = expr.kind; + if method.ident.name == sym!(clone); + if args.len() == 1; + if let Some(arg) = args.get(0); + then { + return get_fixed_offset_var(cx, arg, var); + } + } + + get_fixed_offset_var(cx, expr, var) +} + +fn get_indexed_assignments<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + body: &Expr<'_>, + var: HirId, +) -> Vec<(FixedOffsetVar, FixedOffsetVar)> { + fn get_assignment<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + e: &Expr<'_>, + var: HirId, + ) -> Option<(FixedOffsetVar, FixedOffsetVar)> { + if let ExprKind::Assign(ref lhs, ref rhs, _) = e.kind { + match ( + get_fixed_offset_var(cx, lhs, var), + fetch_cloned_fixed_offset_var(cx, rhs, var), + ) { + (Some(offset_left), Some(offset_right)) => { + // Source and destination must be different + if offset_left.var_name == offset_right.var_name { + None + } else { + Some((offset_left, offset_right)) + } + }, + _ => None, + } + } else { + None + } + } + + if let ExprKind::Block(ref b, _) = body.kind { + let Block { + ref stmts, ref expr, .. + } = **b; + + stmts + .iter() + .map(|stmt| match stmt.kind { + StmtKind::Local(..) | StmtKind::Item(..) => None, + StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => Some(get_assignment(cx, e, var)), + }) + .chain(expr.as_ref().into_iter().map(|e| Some(get_assignment(cx, &*e, var)))) + .filter_map(|op| op) + .collect::>>() + .unwrap_or_default() + } else { + get_assignment(cx, body, var).into_iter().collect() + } +} + +/// Checks for for loops that sequentially copy items from one slice-like +/// object to another. +fn detect_manual_memcpy<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if let Some(higher::Range { + start: Some(start), + ref end, + limits, + }) = higher::range(cx, arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { + let print_sum = |arg1: &Offset, arg2: &Offset| -> String { + match (&arg1.value[..], arg1.negate, &arg2.value[..], arg2.negate) { + ("0", _, "0", _) => "".into(), + ("0", _, x, false) | (x, false, "0", false) => x.into(), + ("0", _, x, true) | (x, false, "0", true) => format!("-{}", x), + (x, false, y, false) => format!("({} + {})", x, y), + (x, false, y, true) => { + if x == y { + "0".into() + } else { + format!("({} - {})", x, y) + } + }, + (x, true, y, false) => { + if x == y { + "0".into() + } else { + format!("({} - {})", y, x) + } + }, + (x, true, y, true) => format!("-({} + {})", x, y), + } + }; + + let print_limit = |end: &Option<&Expr<'_>>, offset: Offset, var_name: &str| { + if let Some(end) = *end { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref len_args) = end.kind; + if method.ident.name == sym!(len); + if len_args.len() == 1; + if let Some(arg) = len_args.get(0); + if snippet(cx, arg.span, "??") == var_name; + then { + return if offset.negate { + format!("({} - {})", snippet(cx, end.span, ".len()"), offset.value) + } else { + String::new() + }; + } + } + + let end_str = match limits { + ast::RangeLimits::Closed => { + let end = sugg::Sugg::hir(cx, end, ""); + format!("{}", end + sugg::ONE) + }, + ast::RangeLimits::HalfOpen => format!("{}", snippet(cx, end.span, "..")), + }; + + print_sum(&Offset::positive(end_str), &offset) + } else { + "..".into() + } + }; + + // The only statements in the for loops can be indexed assignments from + // indexed retrievals. + let manual_copies = get_indexed_assignments(cx, body, canonical_id); + + let big_sugg = manual_copies + .into_iter() + .map(|(dst_var, src_var)| { + let start_str = Offset::positive(snippet(cx, start.span, "").to_string()); + let dst_offset = print_sum(&start_str, &dst_var.offset); + let dst_limit = print_limit(end, dst_var.offset, &dst_var.var_name); + let src_offset = print_sum(&start_str, &src_var.offset); + let src_limit = print_limit(end, src_var.offset, &src_var.var_name); + let dst = if dst_offset == "" && dst_limit == "" { + dst_var.var_name + } else { + format!("{}[{}..{}]", dst_var.var_name, dst_offset, dst_limit) + }; + + format!( + "{}.clone_from_slice(&{}[{}..{}])", + dst, src_var.var_name, src_offset, src_limit + ) + }) + .join("\n "); + + if !big_sugg.is_empty() { + span_lint_and_sugg( + cx, + MANUAL_MEMCPY, + expr.span, + "it looks like you're manually copying between slices", + "try replacing the loop by", + big_sugg, + Applicability::Unspecified, + ); + } + } + } +} + +/// Checks for looping over a range and then indexing a sequence with it. +/// The iteratee must be a range literal. +#[allow(clippy::too_many_lines)] +fn check_for_loop_range<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if let Some(higher::Range { + start: Some(start), + ref end, + limits, + }) = higher::range(cx, arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { + let mut visitor = VarVisitor { + cx, + var: canonical_id, + indexed_mut: FxHashSet::default(), + indexed_indirectly: FxHashMap::default(), + indexed_directly: FxHashMap::default(), + referenced: FxHashSet::default(), + nonindex: false, + prefer_mutable: false, + }; + walk_expr(&mut visitor, body); + + // linting condition: we only indexed one variable, and indexed it directly + if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + let (indexed, (indexed_extent, indexed_ty)) = visitor + .indexed_directly + .into_iter() + .next() + .expect("already checked that we have exactly 1 element"); + + // ensure that the indexed variable was declared before the loop, see #601 + if let Some(indexed_extent) = indexed_extent { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = cx.tcx.hir().local_def_id(parent_id); + let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); + if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { + return; + } + } + + // don't lint if the container that is indexed does not have .iter() method + let has_iter = has_iter_method(cx, indexed_ty); + if has_iter.is_none() { + return; + } + + // don't lint if the container that is indexed into is also used without + // indexing + if visitor.referenced.contains(&indexed) { + return; + } + + let starts_at_zero = is_integer_const(cx, start, 0); + + let skip = if starts_at_zero { + String::new() + } else { + format!(".skip({})", snippet(cx, start.span, "..")) + }; + + let mut end_is_start_plus_val = false; + + let take = if let Some(end) = *end { + let mut take_expr = end; + + if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { + if let BinOpKind::Add = op.node { + let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); + let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); + + if start_equal_left { + take_expr = right; + } else if start_equal_right { + take_expr = left; + } + + end_is_start_plus_val = start_equal_left | start_equal_right; + } + } + + if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { + String::new() + } else { + match limits { + ast::RangeLimits::Closed => { + let take_expr = sugg::Sugg::hir(cx, take_expr, ""); + format!(".take({})", take_expr + sugg::ONE) + }, + ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")), + } + } + } else { + String::new() + }; + + let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { + ("mut ", "iter_mut") + } else { + ("", "iter") + }; + + let take_is_empty = take.is_empty(); + let mut method_1 = take; + let mut method_2 = skip; + + if end_is_start_plus_val { + mem::swap(&mut method_1, &mut method_2); + } + + if visitor.nonindex { + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator".to_string(), + vec![ + (pat.span, format!("({}, )", ident.name)), + ( + arg.span, + format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2), + ), + ], + ); + }, + ); + } else { + let repl = if starts_at_zero && take_is_empty { + format!("&{}{}", ref_mut, indexed) + } else { + format!("{}.{}(){}{}", indexed, method, method_1, method_2) + }; + + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + expr.span, + &format!( + "the loop variable `{}` is only used to index `{}`.", + ident.name, indexed + ), + |diag| { + multispan_sugg( + diag, + "consider using an iterator".to_string(), + vec![(pat.span, "".to_string()), (arg.span, repl)], + ); + }, + ); + } + } + } + } +} + +fn is_len_call(expr: &Expr<'_>, var: Name) -> bool { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref len_args) = expr.kind; + if len_args.len() == 1; + if method.ident.name == sym!(len); + if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; + if path.segments.len() == 1; + if path.segments[0].ident.name == var; + then { + return true; + } + } + + false +} + +fn is_end_eq_array_len<'tcx>( + cx: &LateContext<'_, 'tcx>, + end: &Expr<'_>, + limits: ast::RangeLimits, + indexed_ty: Ty<'tcx>, +) -> bool { + if_chain! { + if let ExprKind::Lit(ref lit) = end.kind; + if let ast::LitKind::Int(end_int, _) = lit.node; + if let ty::Array(_, arr_len_const) = indexed_ty.kind; + if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); + then { + return match limits { + ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), + ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), + }; + } + } + + false +} + +fn check_for_loop_reverse_range<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arg: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { + // if this for loop is iterating over a two-sided range... + if let Some(higher::Range { + start: Some(start), + end: Some(end), + limits, + }) = higher::range(cx, arg) + { + // ...and both sides are compile-time constant integers... + if let Some((start_idx, _)) = constant(cx, cx.tables, start) { + if let Some((end_idx, _)) = constant(cx, cx.tables, end) { + // ...and the start index is greater than the end index, + // this loop will never run. This is often confusing for developers + // who think that this will iterate from the larger value to the + // smaller value. + let ty = cx.tables.expr_ty(start); + let (sup, eq) = match (start_idx, end_idx) { + (Constant::Int(start_idx), Constant::Int(end_idx)) => ( + match ty.kind { + ty::Int(ity) => sext(cx.tcx, start_idx, ity) > sext(cx.tcx, end_idx, ity), + ty::Uint(_) => start_idx > end_idx, + _ => false, + }, + start_idx == end_idx, + ), + _ => (false, false), + }; + + if sup { + let start_snippet = snippet(cx, start.span, "_"); + let end_snippet = snippet(cx, end.span, "_"); + let dots = if limits == ast::RangeLimits::Closed { + "..=" + } else { + ".." + }; + + span_lint_and_then( + cx, + REVERSE_RANGE_LOOP, + expr.span, + "this range is empty so this for loop will never run", + |diag| { + diag.span_suggestion( + arg.span, + "consider using the following if you are attempting to iterate over this \ + range in reverse", + format!( + "({end}{dots}{start}).rev()", + end = end_snippet, + dots = dots, + start = start_snippet + ), + Applicability::MaybeIncorrect, + ); + }, + ); + } else if eq && limits != ast::RangeLimits::Closed { + // if they are equal, it's also problematic - this loop + // will never run. + span_lint( + cx, + REVERSE_RANGE_LOOP, + expr.span, + "this range is empty so this for loop will never run", + ); + } + } + } + } +} + +fn lint_iter_method(cx: &LateContext<'_, '_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + let muta = if method_name == "iter_mut" { "mut " } else { "" }; + span_lint_and_sugg( + cx, + EXPLICIT_ITER_LOOP, + arg.span, + "it is more concise to loop over references to containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + format!("&{}{}", muta, object), + applicability, + ) +} + +fn check_for_loop_arg(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { + let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used + if let ExprKind::MethodCall(ref method, _, ref args) = arg.kind { + // just the receiver, no arguments + if args.len() == 1 { + let method_name = &*method.ident.as_str(); + // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x + if method_name == "iter" || method_name == "iter_mut" { + if is_ref_iterable_type(cx, &args[0]) { + lint_iter_method(cx, args, arg, method_name); + } + } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { + let receiver_ty = cx.tables.expr_ty(&args[0]); + let receiver_ty_adjusted = cx.tables.expr_ty_adjusted(&args[0]); + if same_tys(cx, receiver_ty, receiver_ty_adjusted) { + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + span_lint_and_sugg( + cx, + EXPLICIT_INTO_ITER_LOOP, + arg.span, + "it is more concise to loop over containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + object.to_string(), + applicability, + ); + } else { + let ref_receiver_ty = cx.tcx.mk_ref( + cx.tcx.lifetimes.re_erased, + ty::TypeAndMut { + ty: receiver_ty, + mutbl: Mutability::Not, + }, + ); + if same_tys(cx, receiver_ty_adjusted, ref_receiver_ty) { + lint_iter_method(cx, args, arg, method_name) + } + } + } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { + span_lint( + cx, + ITER_NEXT_LOOP, + expr.span, + "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ + probably not what you want", + ); + next_loop_linted = true; + } + } + } + if !next_loop_linted { + check_arg_type(cx, pat, arg); + } +} + +/// Checks for `for` loops over `Option`s and `Result`s. +fn check_arg_type(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>) { + let ty = cx.tables.expr_ty(arg); + if is_type_diagnostic_item(cx, ty, sym!(option_type)) { + span_lint_and_help( + cx, + FOR_LOOP_OVER_OPTION, + arg.span, + &format!( + "for loop over `{0}`, which is an `Option`. This is more readably written as an \ + `if let` statement.", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } else if is_type_diagnostic_item(cx, ty, sym!(result_type)) { + span_lint_and_help( + cx, + FOR_LOOP_OVER_RESULT, + arg.span, + &format!( + "for loop over `{0}`, which is a `Result`. This is more readably written as an \ + `if let` statement.", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } +} + +fn check_for_loop_explicit_counter<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + // Look for variables that are incremented once per loop iteration. + let mut visitor = IncrementVisitor { + cx, + states: FxHashMap::default(), + depth: 0, + done: false, + }; + walk_expr(&mut visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { + for (id, _) in visitor.states.iter().filter(|&(_, v)| *v == VarState::IncrOnce) { + let mut visitor2 = InitializeVisitor { + cx, + end_expr: expr, + var_id: *id, + state: VarState::IncrOnce, + name: None, + depth: 0, + past_loop: false, + }; + walk_block(&mut visitor2, block); + + if visitor2.state == VarState::Warn { + if let Some(name) = visitor2.name { + let mut applicability = Applicability::MachineApplicable; + + // for some reason this is the only way to get the `Span` + // of the entire `for` loop + let for_span = if let ExprKind::Match(_, arms, _) = &expr.kind { + arms[0].body.span + } else { + unreachable!() + }; + + span_lint_and_sugg( + cx, + EXPLICIT_COUNTER_LOOP, + for_span.with_hi(arg.span.hi()), + &format!("the variable `{}` is used as a loop counter.", name), + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + } + } + } + } +} + +/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the +/// actual `Iterator` that the loop uses. +fn make_iterator_snippet(cx: &LateContext<'_, '_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { + let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR) + .map_or(false, |id| implements_trait(cx, cx.tables.expr_ty(arg), id, &[])); + if impls_iterator { + format!( + "{}", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ) + } else { + // (&x).into_iter() ==> x.iter() + // (&mut x).into_iter() ==> x.iter_mut() + match &arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) + if has_iter_method(cx, cx.tables.expr_ty(&arg_inner)).is_some() => + { + let meth_name = match mutability { + Mutability::Mut => "iter_mut", + Mutability::Not => "iter", + }; + format!( + "{}.{}()", + sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), + meth_name, + ) + } + _ => format!( + "{}.into_iter()", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ), + } + } +} + +/// Checks for the `FOR_KV_MAP` lint. +fn check_for_loop_over_map_kv<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + let pat_span = pat.span; + + if let PatKind::Tuple(ref pat, _) = pat.kind { + if pat.len() == 2 { + let arg_span = arg.span; + let (new_pat_span, kind, ty, mutbl) = match cx.tables.expr_ty(arg).kind { + ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { + (key, _) if pat_is_wild(key, body) => (pat[1].span, "value", ty, mutbl), + (_, value) if pat_is_wild(value, body) => (pat[0].span, "key", ty, Mutability::Not), + _ => return, + }, + _ => return, + }; + let mutbl = match mutbl { + Mutability::Not => "", + Mutability::Mut => "_mut", + }; + let arg = match arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, + _ => arg, + }; + + if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP) { + span_lint_and_then( + cx, + FOR_KV_MAP, + expr.span, + &format!("you seem to want to iterate on a map's {}s", kind), + |diag| { + let map = sugg::Sugg::hir(cx, arg, "map"); + multispan_sugg( + diag, + "use the corresponding method".into(), + vec![ + (pat_span, snippet(cx, new_pat_span, kind).into_owned()), + (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), + ], + ); + }, + ); + } + } + } +} + +struct MutatePairDelegate { + hir_id_low: Option, + hir_id_high: Option, + span_low: Option, + span_high: Option, +} + +impl<'tcx> Delegate<'tcx> for MutatePairDelegate { + fn consume(&mut self, _: &Place<'tcx>, _: ConsumeMode) {} + + fn borrow(&mut self, cmt: &Place<'tcx>, bk: ty::BorrowKind) { + if let ty::BorrowKind::MutBorrow = bk { + if let PlaceBase::Local(id) = cmt.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(cmt.span) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(cmt.span) + } + } + } + } + + fn mutate(&mut self, cmt: &Place<'tcx>) { + if let PlaceBase::Local(id) = cmt.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(cmt.span) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(cmt.span) + } + } + } +} + +impl<'tcx> MutatePairDelegate { + fn mutation_span(&self) -> (Option, Option) { + (self.span_low, self.span_high) + } +} + +fn check_for_mut_range_bound(cx: &LateContext<'_, '_>, arg: &Expr<'_>, body: &Expr<'_>) { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + .. + }) = higher::range(cx, arg) + { + let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; + if mut_ids[0].is_some() || mut_ids[1].is_some() { + let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); + mut_warn_with_span(cx, span_low); + mut_warn_with_span(cx, span_high); + } + } +} + +fn mut_warn_with_span(cx: &LateContext<'_, '_>, span: Option) { + if let Some(sp) = span { + span_lint( + cx, + MUT_RANGE_BOUND, + sp, + "attempt to mutate range bound within loop; note that the range of the loop is unchanged", + ); + } +} + +fn check_for_mutability(cx: &LateContext<'_, '_>, bound: &Expr<'_>) -> Option { + if_chain! { + if let ExprKind::Path(ref qpath) = bound.kind; + if let QPath::Resolved(None, _) = *qpath; + then { + let res = qpath_res(cx, qpath, bound.hir_id); + if let Res::Local(hir_id) = res { + let node_str = cx.tcx.hir().get(hir_id); + if_chain! { + if let Node::Binding(pat) = node_str; + if let PatKind::Binding(bind_ann, ..) = pat.kind; + if let BindingAnnotation::Mutable = bind_ann; + then { + return Some(hir_id); + } + } + } + } + } + None +} + +fn check_for_mutation( + cx: &LateContext<'_, '_>, + body: &Expr<'_>, + bound_ids: &[Option], +) -> (Option, Option) { + let mut delegate = MutatePairDelegate { + hir_id_low: bound_ids[0], + hir_id_high: bound_ids[1], + span_low: None, + span_high: None, + }; + let def_id = body.hir_id.owner.to_def_id(); + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new(&mut delegate, &infcx, def_id.expect_local(), cx.param_env, cx.tables).walk_expr(body); + }); + delegate.mutation_span() +} + +/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. +fn pat_is_wild<'tcx>(pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { + match *pat { + PatKind::Wild => true, + PatKind::Binding(.., ident, None) if ident.as_str().starts_with('_') => is_unused(&ident, body), + _ => false, + } +} + +struct LocalUsedVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + local: HirId, + used: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for LocalUsedVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if same_var(self.cx, expr, self.local) { + self.used = true; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +struct VarVisitor<'a, 'tcx> { + /// context reference + cx: &'a LateContext<'a, 'tcx>, + /// var name to look for as index + var: HirId, + /// indexed variables that are used mutably + indexed_mut: FxHashSet, + /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global + indexed_indirectly: FxHashMap>, + /// subset of `indexed` of vars that are indexed directly: `v[i]` + /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` + indexed_directly: FxHashMap, Ty<'tcx>)>, + /// Any names that are used outside an index operation. + /// Used to detect things like `&mut vec` used together with `vec[i]` + referenced: FxHashSet, + /// has the loop variable been used in expressions other than the index of + /// an index op? + nonindex: bool, + /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar + /// takes `&mut self` + prefer_mutable: bool, +} + +impl<'a, 'tcx> VarVisitor<'a, 'tcx> { + fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + if_chain! { + // the indexed container is referenced by a name + if let ExprKind::Path(ref seqpath) = seqexpr.kind; + if let QPath::Resolved(None, ref seqvar) = *seqpath; + if seqvar.segments.len() == 1; + then { + let index_used_directly = same_var(self.cx, idx, self.var); + let indexed_indirectly = { + let mut used_visitor = LocalUsedVisitor { + cx: self.cx, + local: self.var, + used: false, + }; + walk_expr(&mut used_visitor, idx); + used_visitor.used + }; + + if indexed_indirectly || index_used_directly { + if self.prefer_mutable { + self.indexed_mut.insert(seqvar.segments[0].ident.name); + } + let res = qpath_res(self.cx, seqpath, seqexpr.hir_id); + match res { + Res::Local(hir_id) => { + let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); + let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (Some(extent), self.cx.tables.node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + Res::Def(DefKind::Static | DefKind::Const, ..) => { + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (None, self.cx.tables.node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + _ => (), + } + } + } + } + true + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + // a range index op + if let ExprKind::MethodCall(ref meth, _, ref args) = expr.kind; + if (meth.ident.name == sym!(index) && match_trait_method(self.cx, expr, &paths::INDEX)) + || (meth.ident.name == sym!(index_mut) && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); + if !self.check(&args[1], &args[0], expr); + then { return } + } + + if_chain! { + // an index op + if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; + if !self.check(idx, seqexpr, expr); + then { return } + } + + if_chain! { + // directly using a variable + if let ExprKind::Path(ref qpath) = expr.kind; + if let QPath::Resolved(None, ref path) = *qpath; + if path.segments.len() == 1; + then { + if let Res::Local(local_id) = qpath_res(self.cx, qpath, expr.hir_id) { + if local_id == self.var { + self.nonindex = true; + } else { + // not the correct variable, but still a variable + self.referenced.insert(path.segments[0].ident.name); + } + } + } + } + + let old = self.prefer_mutable; + match expr.kind { + ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { + self.prefer_mutable = true; + self.visit_expr(lhs); + self.prefer_mutable = false; + self.visit_expr(rhs); + }, + ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + self.visit_expr(expr); + }, + ExprKind::Call(ref f, args) => { + self.visit_expr(f); + for expr in args { + let ty = self.cx.tables.expr_ty_adjusted(expr); + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = ty.kind { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::MethodCall(_, _, args) => { + let def_id = self.cx.tables.type_dependent_def_id(expr.hir_id).unwrap(); + for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) { + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = ty.kind { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::Closure(_, _, body_id, ..) => { + let body = self.cx.tcx.hir().body(body_id); + self.visit_expr(&body.value); + }, + _ => walk_expr(self, expr), + } + self.prefer_mutable = old; + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn is_used_inside<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { + let def_id = match var_def_id(cx, expr) { + Some(id) => id, + None => return false, + }; + if let Some(used_mutably) = mutated_variables(container, cx) { + if used_mutably.contains(&def_id) { + return true; + } + } + false +} + +fn is_iterator_used_after_while_let<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { + let def_id = match var_def_id(cx, iter_expr) { + Some(id) => id, + None => return false, + }; + let mut visitor = VarUsedAfterLoopVisitor { + cx, + def_id, + iter_expr_id: iter_expr.hir_id, + past_while_let: false, + var_used_after_while_let: false, + }; + if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { + walk_block(&mut visitor, enclosing_block); + } + visitor.var_used_after_while_let +} + +struct VarUsedAfterLoopVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + def_id: HirId, + iter_expr_id: HirId, + past_while_let: bool, + var_used_after_while_let: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.past_while_let { + if Some(self.def_id) == var_def_id(self.cx, expr) { + self.var_used_after_while_let = true; + } + } else if self.iter_expr_id == expr.hir_id { + self.past_while_let = true; + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Returns `true` if the type of expr is one that provides `IntoIterator` impls +/// for `&T` and `&mut T`, such as `Vec`. +#[rustfmt::skip] +fn is_ref_iterable_type(cx: &LateContext<'_, '_>, e: &Expr<'_>) -> bool { + // no walk_ptrs_ty: calling iter() on a reference can make sense because it + // will allow further borrows afterwards + let ty = cx.tables.expr_ty(e); + is_iterable_array(ty, cx) || + is_type_diagnostic_item(cx, ty, sym!(vec_type)) || + match_type(cx, ty, &paths::LINKED_LIST) || + is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || + is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::BINARY_HEAP) || + match_type(cx, ty, &paths::BTREEMAP) || + match_type(cx, ty, &paths::BTREESET) +} + +fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'_, 'tcx>) -> bool { + // IntoIterator is currently only implemented for array sizes <= 32 in rustc + match ty.kind { + ty::Array(_, n) => { + if let Some(val) = n.try_eval_usize(cx.tcx, cx.param_env) { + (0..=32).contains(&val) + } else { + false + } + }, + _ => false, + } +} + +/// If a block begins with a statement (possibly a `let` binding) and has an +/// expression, return it. +fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if block.stmts.is_empty() { + return None; + } + if let StmtKind::Local(ref local) = block.stmts[0].kind { + if let Some(expr) = local.init { + Some(expr) + } else { + None + } + } else { + None + } +} + +/// If a block begins with an expression (with or without semicolon), return it. +fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match block.expr { + Some(ref expr) if block.stmts.is_empty() => Some(expr), + None if !block.stmts.is_empty() => match block.stmts[0].kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), + StmtKind::Local(..) | StmtKind::Item(..) => None, + }, + _ => None, + } +} + +/// Returns `true` if expr contains a single break expr without destination label +/// and +/// passed expression. The expression may be within a block. +fn is_simple_break_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, + ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), + _ => false, + } +} + +// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be +// incremented exactly once in the loop body, and initialized to zero +// at the start of the loop. +#[derive(Debug, PartialEq)] +enum VarState { + Initial, // Not examined yet + IncrOnce, // Incremented exactly once, may be a loop counter + Declared, // Declared but not (yet) initialized to zero + Warn, + DontWarn, +} + +/// Scan a for loop for variables that are incremented exactly once. +struct IncrementVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, // context reference + states: FxHashMap, // incremented variables + depth: u32, // depth of conditional expressions + done: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.done { + return; + } + + // If node is a variable + if let Some(def_id) = var_def_id(self.cx, expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + let state = self.states.entry(def_id).or_insert(VarState::Initial); + + match parent.kind { + ExprKind::AssignOp(op, ref lhs, ref rhs) => { + if lhs.hir_id == expr.hir_id { + if op.node == BinOpKind::Add && is_integer_const(self.cx, rhs, 1) { + *state = match *state { + VarState::Initial if self.depth == 0 => VarState::IncrOnce, + _ => VarState::DontWarn, + }; + } else { + // Assigned some other value + *state = VarState::DontWarn; + } + } + }, + ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => *state = VarState::DontWarn, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + *state = VarState::DontWarn + }, + _ => (), + } + } + } else if is_loop(expr) || is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + return; + } else if let ExprKind::Continue(_) = expr.kind { + self.done = true; + return; + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Checks whether a variable is initialized to zero at the start of a loop. +struct InitializeVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, // context reference + end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. + var_id: HirId, + state: VarState, + name: Option, + depth: u32, // depth of conditional expressions + past_loop: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + // Look for declarations of the variable + if let StmtKind::Local(ref local) = stmt.kind { + if local.pat.hir_id == self.var_id { + if let PatKind::Binding(.., ident, _) = local.pat.kind { + self.name = Some(ident.name); + + self.state = if let Some(ref init) = local.init { + if is_integer_const(&self.cx, init, 0) { + VarState::Warn + } else { + VarState::Declared + } + } else { + VarState::Declared + } + } + } + } + walk_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.state == VarState::DontWarn { + return; + } + if SpanlessEq::new(self.cx).eq_expr(&expr, self.end_expr) { + self.past_loop = true; + return; + } + // No need to visit expressions before the variable is + // declared + if self.state == VarState::IncrOnce { + return; + } + + // If node is the desired variable, see how it's used + if var_def_id(self.cx, expr) == Some(self.var_id) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { + self.state = VarState::DontWarn; + }, + ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { + self.state = if is_integer_const(&self.cx, rhs, 0) && self.depth == 0 { + VarState::Warn + } else { + VarState::DontWarn + } + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + self.state = VarState::DontWarn + }, + _ => (), + } + } + + if self.past_loop { + self.state = VarState::DontWarn; + return; + } + } else if !self.past_loop && is_loop(expr) { + self.state = VarState::DontWarn; + return; + } else if is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + return; + } + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +fn var_def_id(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { + if let ExprKind::Path(ref qpath) = expr.kind { + let path_res = qpath_res(cx, qpath, expr.hir_id); + if let Res::Local(hir_id) = path_res { + return Some(hir_id); + } + } + None +} + +fn is_loop(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Loop(..) => true, + _ => false, + } +} + +fn is_conditional(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Match(..) => true, + _ => false, + } +} + +fn is_nested(cx: &LateContext<'_, '_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + if_chain! { + if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); + let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); + if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); + then { + return is_loop_nested(cx, loop_expr, iter_expr) + } + } + false +} + +fn is_loop_nested(cx: &LateContext<'_, '_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + let mut id = loop_expr.hir_id; + let iter_name = if let Some(name) = path_name(iter_expr) { + name + } else { + return true; + }; + loop { + let parent = cx.tcx.hir().get_parent_node(id); + if parent == id { + return false; + } + match cx.tcx.hir().find(parent) { + Some(Node::Expr(expr)) => { + if let ExprKind::Loop(..) = expr.kind { + return true; + }; + }, + Some(Node::Block(block)) => { + let mut block_visitor = LoopNestVisitor { + hir_id: id, + iterator: iter_name, + nesting: Unknown, + }; + walk_block(&mut block_visitor, block); + if block_visitor.nesting == RuledOut { + return false; + } + }, + Some(Node::Stmt(_)) => (), + _ => { + return false; + }, + } + id = parent; + } +} + +#[derive(PartialEq, Eq)] +enum Nesting { + Unknown, // no nesting detected yet + RuledOut, // the iterator is initialized or assigned within scope + LookFurther, // no nesting detected, no further walk required +} + +use self::Nesting::{LookFurther, RuledOut, Unknown}; + +struct LoopNestVisitor { + hir_id: HirId, + iterator: Name, + nesting: Nesting, +} + +impl<'tcx> Visitor<'tcx> for LoopNestVisitor { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + if stmt.hir_id == self.hir_id { + self.nesting = LookFurther; + } else if self.nesting == Unknown { + walk_stmt(self, stmt); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.nesting != Unknown { + return; + } + if expr.hir_id == self.hir_id { + self.nesting = LookFurther; + return; + } + match expr.kind { + ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { + if match_var(path, self.iterator) { + self.nesting = RuledOut; + } + }, + _ => walk_expr(self, expr), + } + } + + fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { + if self.nesting != Unknown { + return; + } + if let PatKind::Binding(.., span_name, _) = pat.kind { + if self.iterator == span_name.name { + self.nesting = RuledOut; + return; + } + } + walk_pat(self, pat) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn path_name(e: &Expr<'_>) -> Option { + if let ExprKind::Path(QPath::Resolved(_, ref path)) = e.kind { + let segments = &path.segments; + if segments.len() == 1 { + return Some(segments[0].ident.name); + } + }; + None +} + +fn check_infinite_loop<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { + if constant(cx, cx.tables, cond).is_some() { + // A pure constant condition (e.g., `while false`) is not linted. + return; + } + + let mut var_visitor = VarCollectorVisitor { + cx, + ids: FxHashSet::default(), + def_ids: FxHashMap::default(), + skip: false, + }; + var_visitor.visit_expr(cond); + if var_visitor.skip { + return; + } + let used_in_condition = &var_visitor.ids; + let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { + used_in_condition.is_disjoint(&used_mutably) + } else { + return; + }; + let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); + + let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { + has_break_or_return: false, + }; + has_break_or_return_visitor.visit_expr(expr); + let has_break_or_return = has_break_or_return_visitor.has_break_or_return; + + if no_cond_variable_mutated && !mutable_static_in_cond { + span_lint_and_then( + cx, + WHILE_IMMUTABLE_CONDITION, + cond.span, + "variables in the condition are not mutated in the loop body", + |diag| { + diag.note("this may lead to an infinite or to a never running loop"); + + if has_break_or_return { + diag.note("this loop contains `return`s or `break`s"); + diag.help("rewrite it as `if cond { loop { } }`"); + } + }, + ); + } +} + +struct HasBreakOrReturnVisitor { + has_break_or_return: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.has_break_or_return { + return; + } + + match expr.kind { + ExprKind::Ret(_) | ExprKind::Break(_, _) => { + self.has_break_or_return = true; + return; + }, + _ => {}, + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Collects the set of variables in an expression +/// Stops analysis if a function call is found +/// Note: In some cases such as `self`, there are no mutable annotation, +/// All variables definition IDs are collected +struct VarCollectorVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + ids: FxHashSet, + def_ids: FxHashMap, + skip: bool, +} + +impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { + fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Path(ref qpath) = ex.kind; + if let QPath::Resolved(None, _) = *qpath; + let res = qpath_res(self.cx, qpath, ex.hir_id); + then { + match res { + Res::Local(hir_id) => { + self.ids.insert(hir_id); + }, + Res::Def(DefKind::Static, def_id) => { + let mutable = self.cx.tcx.is_mutable_static(def_id); + self.def_ids.insert(def_id, mutable); + }, + _ => {}, + } + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + match ex.kind { + ExprKind::Path(_) => self.insert_def_id(ex), + // If there is any function/method call… we just stop analysis + ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + + _ => walk_expr(self, ex), + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; + +fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, 'tcx>) { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref args) = expr.kind; + if let ExprKind::MethodCall(ref chain_method, _, _) = args[0].kind; + if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR); + if let Some(ref generic_args) = chain_method.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + then { + let ty = cx.tables.node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym!(vec_type)) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::BTREEMAP) || + is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) { + if method.ident.name == sym!(len) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + ".count()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(is_empty) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + ".next().is_none()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(contains) { + let contains_arg = snippet(cx, args[1].span, "??"); + let span = shorten_needless_collect_span(expr); + span_lint_and_then( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + |diag| { + let (arg, pred) = if contains_arg.starts_with('&') { + ("x", &contains_arg[1..]) + } else { + ("&x", &*contains_arg) + }; + diag.span_suggestion( + span, + "replace with", + format!( + ".any(|{}| x == {})", + arg, pred + ), + Applicability::MachineApplicable, + ); + } + ); + } + } + } + } +} + +fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { + if_chain! { + if let ExprKind::MethodCall(_, _, ref args) = expr.kind; + if let ExprKind::MethodCall(_, ref span, _) = args[0].kind; + then { + return expr.span.with_lo(span.lo() - BytePos(1)); + } + } + unreachable!() +} diff --git a/src/tools/clippy/clippy_lints/src/macro_use.rs b/src/tools/clippy/clippy_lints/src/macro_use.rs new file mode 100644 index 0000000000000..b1345d0b751ae --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/macro_use.rs @@ -0,0 +1,53 @@ +use crate::utils::{snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::edition::Edition; + +declare_clippy_lint! { + /// **What it does:** Checks for `#[macro_use] use...`. + /// + /// **Why is this bad?** Since the Rust 2018 edition you can import + /// macro's directly, this is considered idiomatic. + /// + /// **Known problems:** This lint does not generate an auto-applicable suggestion. + /// + /// **Example:** + /// ```rust + /// #[macro_use] + /// use lazy_static; + /// ``` + pub MACRO_USE_IMPORTS, + pedantic, + "#[macro_use] is no longer needed" +} + +declare_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]); + +impl EarlyLintPass for MacroUseImports { + fn check_item(&mut self, ecx: &EarlyContext<'_>, item: &ast::Item) { + if_chain! { + if ecx.sess.opts.edition == Edition::Edition2018; + if let ast::ItemKind::Use(use_tree) = &item.kind; + if let Some(mac_attr) = item + .attrs + .iter() + .find(|attr| attr.ident().map(|s| s.to_string()) == Some("macro_use".to_string())); + then { + let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition"; + let help = format!("use {}::", snippet(ecx, use_tree.span, "_")); + span_lint_and_sugg( + ecx, + MACRO_USE_IMPORTS, + mac_attr.span, + msg, + "remove the attribute and import the macro directly, try", + help, + Applicability::HasPlaceholders, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/main_recursion.rs b/src/tools/clippy/clippy_lints/src/main_recursion.rs new file mode 100644 index 0000000000000..8a0e47a3d31c5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/main_recursion.rs @@ -0,0 +1,62 @@ +use rustc_hir::{Crate, Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use crate::utils::{is_entrypoint_fn, is_no_std_crate, snippet, span_lint_and_help}; +use if_chain::if_chain; + +declare_clippy_lint! { + /// **What it does:** Checks for recursion using the entrypoint. + /// + /// **Why is this bad?** Apart from special setups (which we could detect following attributes like #![no_std]), + /// recursing into main() seems like an unintuitive antipattern we should be able to detect. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// fn main() { + /// main(); + /// } + /// ``` + pub MAIN_RECURSION, + style, + "recursion using the entrypoint" +} + +#[derive(Default)] +pub struct MainRecursion { + has_no_std_attr: bool, +} + +impl_lint_pass!(MainRecursion => [MAIN_RECURSION]); + +impl LateLintPass<'_, '_> for MainRecursion { + fn check_crate(&mut self, _: &LateContext<'_, '_>, krate: &Crate<'_>) { + self.has_no_std_attr = is_no_std_crate(krate); + } + + fn check_expr_post(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if self.has_no_std_attr { + return; + } + + if_chain! { + if let ExprKind::Call(func, _) = &expr.kind; + if let ExprKind::Path(path) = &func.kind; + if let QPath::Resolved(_, path) = &path; + if let Some(def_id) = path.res.opt_def_id(); + if is_entrypoint_fn(cx, def_id); + then { + span_lint_and_help( + cx, + MAIN_RECURSION, + func.span, + &format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")), + None, + "consider using another function for this recursion" + ) + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/map_clone.rs b/src/tools/clippy/clippy_lints/src/map_clone.rs new file mode 100644 index 0000000000000..0b346393ac389 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/map_clone.rs @@ -0,0 +1,150 @@ +use crate::utils::paths; +use crate::utils::{ + is_copy, is_type_diagnostic_item, match_trait_method, remove_blocks, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast::Ident; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::Mutability; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `iterator.map(|x| x.clone())` and suggests + /// `iterator.cloned()` instead + /// + /// **Why is this bad?** Readability, this can be written more concisely + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.map(|i| *i); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.cloned(); + /// ``` + pub MAP_CLONE, + style, + "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" +} + +declare_lint_pass!(MapClone => [MAP_CLONE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MapClone { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, e: &hir::Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if_chain! { + if let hir::ExprKind::MethodCall(ref method, _, ref args) = e.kind; + if args.len() == 2; + if method.ident.as_str() == "map"; + let ty = cx.tables.expr_ty(&args[0]); + if is_type_diagnostic_item(cx, ty, sym!(option_type)) || match_trait_method(cx, e, &paths::ITERATOR); + if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind; + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + then { + match closure_body.params[0].pat.kind { + hir::PatKind::Ref(ref inner, hir::Mutability::Not) => if let hir::PatKind::Binding( + hir::BindingAnnotation::Unannotated, .., name, None + ) = inner.kind { + if ident_eq(name, closure_expr) { + lint(cx, e.span, args[0].span, true); + } + }, + hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { + match closure_expr.kind { + hir::ExprKind::Unary(hir::UnOp::UnDeref, ref inner) => { + if ident_eq(name, inner) { + if let ty::Ref(.., Mutability::Not) = cx.tables.expr_ty(inner).kind { + lint(cx, e.span, args[0].span, true); + } + } + }, + hir::ExprKind::MethodCall(ref method, _, ref obj) => { + if ident_eq(name, &obj[0]) && method.ident.as_str() == "clone" + && match_trait_method(cx, closure_expr, &paths::CLONE_TRAIT) { + + let obj_ty = cx.tables.expr_ty(&obj[0]); + if let ty::Ref(_, ty, _) = obj_ty.kind { + let copy = is_copy(cx, ty); + lint(cx, e.span, args[0].span, copy); + } else { + lint_needless_cloning(cx, e.span, args[0].span); + } + } + }, + _ => {}, + } + }, + _ => {}, + } + } + } + } +} + +fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Path(hir::QPath::Resolved(None, ref path)) = path.kind { + path.segments.len() == 1 && path.segments[0].ident == name + } else { + false + } +} + +fn lint_needless_cloning(cx: &LateContext<'_, '_>, root: Span, receiver: Span) { + span_lint_and_sugg( + cx, + MAP_CLONE, + root.trim_start(receiver).unwrap(), + "You are needlessly cloning iterator elements", + "Remove the `map` call", + String::new(), + Applicability::MachineApplicable, + ) +} + +fn lint(cx: &LateContext<'_, '_>, replace: Span, root: Span, copied: bool) { + let mut applicability = Applicability::MachineApplicable; + if copied { + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + "You are using an explicit closure for copying elements", + "Consider calling the dedicated `copied` method", + format!( + "{}.copied()", + snippet_with_applicability(cx, root, "..", &mut applicability) + ), + applicability, + ) + } else { + span_lint_and_sugg( + cx, + MAP_CLONE, + replace, + "You are using an explicit closure for cloning elements", + "Consider calling the dedicated `cloned` method", + format!( + "{}.cloned()", + snippet_with_applicability(cx, root, "..", &mut applicability) + ), + applicability, + ) + } +} diff --git a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs new file mode 100644 index 0000000000000..fecd91c7814dc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs @@ -0,0 +1,273 @@ +use crate::utils::{is_type_diagnostic_item, iter_input_pats, method_chain_args, snippet, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `option.map(f)` where f is a function + /// or closure that returns the unit type. + /// + /// **Why is this bad?** Readability, this can be written more clearly with + /// an if let statement + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # fn do_stuff() -> Option { Some(String::new()) } + /// # fn log_err_msg(foo: String) -> Option { Some(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Option = do_stuff(); + /// x.map(log_err_msg); + /// # let x: Option = do_stuff(); + /// x.map(|msg| log_err_msg(format_msg(msg))); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// # fn do_stuff() -> Option { Some(String::new()) } + /// # fn log_err_msg(foo: String) -> Option { Some(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Option = do_stuff(); + /// if let Some(msg) = x { + /// log_err_msg(msg); + /// } + /// + /// # let x: Option = do_stuff(); + /// if let Some(msg) = x { + /// log_err_msg(format_msg(msg)); + /// } + /// ``` + pub OPTION_MAP_UNIT_FN, + complexity, + "using `option.map(f)`, where `f` is a function or closure that returns `()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `result.map(f)` where f is a function + /// or closure that returns the unit type. + /// + /// **Why is this bad?** Readability, this can be written more clearly with + /// an if let statement + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # fn do_stuff() -> Result { Ok(String::new()) } + /// # fn log_err_msg(foo: String) -> Result { Ok(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Result = do_stuff(); + /// x.map(log_err_msg); + /// # let x: Result = do_stuff(); + /// x.map(|msg| log_err_msg(format_msg(msg))); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// # fn do_stuff() -> Result { Ok(String::new()) } + /// # fn log_err_msg(foo: String) -> Result { Ok(foo) } + /// # fn format_msg(foo: String) -> String { String::new() } + /// let x: Result = do_stuff(); + /// if let Ok(msg) = x { + /// log_err_msg(msg); + /// }; + /// # let x: Result = do_stuff(); + /// if let Ok(msg) = x { + /// log_err_msg(format_msg(msg)); + /// }; + /// ``` + pub RESULT_MAP_UNIT_FN, + complexity, + "using `result.map(f)`, where `f` is a function or closure that returns `()`" +} + +declare_lint_pass!(MapUnit => [OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN]); + +fn is_unit_type(ty: Ty<'_>) -> bool { + match ty.kind { + ty::Tuple(slice) => slice.is_empty(), + ty::Never => true, + _ => false, + } +} + +fn is_unit_function(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) -> bool { + let ty = cx.tables.expr_ty(expr); + + if let ty::FnDef(id, _) = ty.kind { + if let Some(fn_type) = cx.tcx.fn_sig(id).no_bound_vars() { + return is_unit_type(fn_type.output()); + } + } + false +} + +fn is_unit_expression(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) -> bool { + is_unit_type(cx.tables.expr_ty(expr)) +} + +/// The expression inside a closure may or may not have surrounding braces and +/// semicolons, which causes problems when generating a suggestion. Given an +/// expression that evaluates to '()' or '!', recursively remove useless braces +/// and semi-colons until is suitable for including in the suggestion template +fn reduce_unit_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a hir::Expr<'_>) -> Option { + if !is_unit_expression(cx, expr) { + return None; + } + + match expr.kind { + hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(_, _, _) => { + // Calls can't be reduced any more + Some(expr.span) + }, + hir::ExprKind::Block(ref block, _) => { + match (&block.stmts[..], block.expr.as_ref()) { + (&[], Some(inner_expr)) => { + // If block only contains an expression, + // reduce `{ X }` to `X` + reduce_unit_expression(cx, inner_expr) + }, + (&[ref inner_stmt], None) => { + // If block only contains statements, + // reduce `{ X; }` to `X` or `X;` + match inner_stmt.kind { + hir::StmtKind::Local(ref local) => Some(local.span), + hir::StmtKind::Expr(ref e) => Some(e.span), + hir::StmtKind::Semi(..) => Some(inner_stmt.span), + hir::StmtKind::Item(..) => None, + } + }, + _ => { + // For closures that contain multiple statements + // it's difficult to get a correct suggestion span + // for all cases (multi-line closures specifically) + // + // We do not attempt to build a suggestion for those right now. + None + }, + } + }, + _ => None, + } +} + +fn unit_closure<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'a hir::Expr<'a>, +) -> Option<(&'tcx hir::Param<'tcx>, &'a hir::Expr<'a>)> { + if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.kind { + let body = cx.tcx.hir().body(inner_expr_id); + let body_expr = &body.value; + + if_chain! { + if decl.inputs.len() == 1; + if is_unit_expression(cx, body_expr); + if let Some(binding) = iter_input_pats(&decl, body).next(); + then { + return Some((binding, body_expr)); + } + } + } + None +} + +/// Builds a name for the let binding variable (`var_arg`) +/// +/// `x.field` => `x_field` +/// `y` => `_y` +/// +/// Anything else will return `a`. +fn let_binding_name(cx: &LateContext<'_, '_>, var_arg: &hir::Expr<'_>) -> String { + match &var_arg.kind { + hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace(".", "_"), + hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")), + _ => "a".to_string(), + } +} + +#[must_use] +fn suggestion_msg(function_type: &str, map_type: &str) -> String { + format!( + "called `map(f)` on an `{0}` value where `f` is a {1} that returns the unit type", + map_type, function_type + ) +} + +fn lint_map_unit_fn(cx: &LateContext<'_, '_>, stmt: &hir::Stmt<'_>, expr: &hir::Expr<'_>, map_args: &[hir::Expr<'_>]) { + let var_arg = &map_args[0]; + + let (map_type, variant, lint) = if is_type_diagnostic_item(cx, cx.tables.expr_ty(var_arg), sym!(option_type)) { + ("Option", "Some", OPTION_MAP_UNIT_FN) + } else if is_type_diagnostic_item(cx, cx.tables.expr_ty(var_arg), sym!(result_type)) { + ("Result", "Ok", RESULT_MAP_UNIT_FN) + } else { + return; + }; + let fn_arg = &map_args[1]; + + if is_unit_function(cx, fn_arg) { + let msg = suggestion_msg("function", map_type); + let suggestion = format!( + "if let {0}({binding}) = {1} {{ {2}({binding}) }}", + variant, + snippet(cx, var_arg.span, "_"), + snippet(cx, fn_arg.span, "_"), + binding = let_binding_name(cx, var_arg) + ); + + span_lint_and_then(cx, lint, expr.span, &msg, |diag| { + diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::MachineApplicable); + }); + } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) { + let msg = suggestion_msg("closure", map_type); + + span_lint_and_then(cx, lint, expr.span, &msg, |diag| { + if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) { + let suggestion = format!( + "if let {0}({1}) = {2} {{ {3} }}", + variant, + snippet(cx, binding.pat.span, "_"), + snippet(cx, var_arg.span, "_"), + snippet(cx, reduced_expr_span, "_") + ); + diag.span_suggestion( + stmt.span, + "try this", + suggestion, + Applicability::MachineApplicable, // snippet + ); + } else { + let suggestion = format!( + "if let {0}({1}) = {2} {{ ... }}", + variant, + snippet(cx, binding.pat.span, "_"), + snippet(cx, var_arg.span, "_"), + ); + diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::HasPlaceholders); + } + }); + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MapUnit { + fn check_stmt(&mut self, cx: &LateContext<'_, '_>, stmt: &hir::Stmt<'_>) { + if stmt.span.from_expansion() { + return; + } + + if let hir::StmtKind::Semi(ref expr) = stmt.kind { + if let Some(arglists) = method_chain_args(expr, &["map"]) { + lint_map_unit_fn(cx, stmt, expr, arglists[0]); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/match_on_vec_items.rs b/src/tools/clippy/clippy_lints/src/match_on_vec_items.rs new file mode 100644 index 0000000000000..4071406cc84c3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/match_on_vec_items.rs @@ -0,0 +1,89 @@ +use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_sugg, walk_ptrs_ty}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `match vec[idx]` or `match vec[n..m]`. + /// + /// **Why is this bad?** This can panic at runtime. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust, no_run + /// let arr = vec![0, 1, 2, 3]; + /// let idx = 1; + /// + /// // Bad + /// match arr[idx] { + /// 0 => println!("{}", 0), + /// 1 => println!("{}", 3), + /// _ => {}, + /// } + /// ``` + /// Use instead: + /// ```rust, no_run + /// let arr = vec![0, 1, 2, 3]; + /// let idx = 1; + /// + /// // Good + /// match arr.get(idx) { + /// Some(0) => println!("{}", 0), + /// Some(1) => println!("{}", 3), + /// _ => {}, + /// } + /// ``` + pub MATCH_ON_VEC_ITEMS, + correctness, + "matching on vector elements can panic" +} + +declare_lint_pass!(MatchOnVecItems => [MATCH_ON_VEC_ITEMS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MatchOnVecItems { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) { + if_chain! { + if !in_external_macro(cx.sess(), expr.span); + if let ExprKind::Match(ref match_expr, _, MatchSource::Normal) = expr.kind; + if let Some(idx_expr) = is_vec_indexing(cx, match_expr); + if let ExprKind::Index(vec, idx) = idx_expr.kind; + + then { + // FIXME: could be improved to suggest surrounding every pattern with Some(_), + // but only when `or_patterns` are stabilized. + span_lint_and_sugg( + cx, + MATCH_ON_VEC_ITEMS, + match_expr.span, + "indexing into a vector may panic", + "try this", + format!( + "{}.get({})", + snippet(cx, vec.span, ".."), + snippet(cx, idx.span, "..") + ), + Applicability::MaybeIncorrect + ); + } + } + } +} + +fn is_vec_indexing<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if_chain! { + if let ExprKind::Index(ref array, _) = expr.kind; + let ty = cx.tables.expr_ty(array); + let ty = walk_ptrs_ty(ty); + if is_type_diagnostic_item(cx, ty, sym!(vec_type)); + + then { + return Some(expr); + } + } + + None +} diff --git a/src/tools/clippy/clippy_lints/src/matches.rs b/src/tools/clippy/clippy_lints/src/matches.rs new file mode 100644 index 0000000000000..8f86535ef1e0f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/matches.rs @@ -0,0 +1,1242 @@ +use crate::consts::{constant, miri_to_const, Constant}; +use crate::utils::paths; +use crate::utils::sugg::Sugg; +use crate::utils::usage::is_unused; +use crate::utils::{ + expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable, + is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, multispan_sugg, remove_blocks, snippet, + snippet_block, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, + span_lint_and_then, walk_ptrs_ty, +}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::def::CtorKind; +use rustc_hir::{ + Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, + QPath, RangeEnd, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use std::cmp::Ordering; +use std::collections::Bound; + +declare_clippy_lint! { + /// **What it does:** Checks for matches with a single arm where an `if let` + /// will usually suffice. + /// + /// **Why is this bad?** Just readability – `if let` nests less than a `match`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn bar(stool: &str) {} + /// # let x = Some("abc"); + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => (), + /// } + /// ``` + pub SINGLE_MATCH, + style, + "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches with two arms where an `if let else` will + /// usually suffice. + /// + /// **Why is this bad?** Just readability – `if let` nests less than a `match`. + /// + /// **Known problems:** Personal style preferences may differ. + /// + /// **Example:** + /// + /// Using `match`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => bar(&other_ref), + /// } + /// ``` + /// + /// Using `if let` with `else`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// if let Some(ref foo) = x { + /// bar(foo); + /// } else { + /// bar(&other_ref); + /// } + /// ``` + pub SINGLE_MATCH_ELSE, + pedantic, + "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches where all arms match a reference, + /// suggesting to remove the reference and deref the matched expression + /// instead. It also checks for `if let &foo = bar` blocks. + /// + /// **Why is this bad?** It just makes the code less readable. That reference + /// destructuring adds nothing to the code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// match x { + /// &A(ref y) => foo(y), + /// &B => bar(), + /// _ => frob(&x), + /// } + /// ``` + pub MATCH_REF_PATS, + style, + "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches where match expression is a `bool`. It + /// suggests to replace the expression with an `if...else` block. + /// + /// **Why is this bad?** It makes the code less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// match condition { + /// true => foo(), + /// false => bar(), + /// } + /// ``` + /// Use if/else instead: + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// if condition { + /// foo(); + /// } else { + /// bar(); + /// } + /// ``` + pub MATCH_BOOL, + pedantic, + "a `match` on a boolean expression instead of an `if..else` block" +} + +declare_clippy_lint! { + /// **What it does:** Checks for overlapping match arms. + /// + /// **Why is this bad?** It is likely to be an error and if not, makes the code + /// less obvious. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 5; + /// match x { + /// 1...10 => println!("1 ... 10"), + /// 5...15 => println!("5 ... 15"), + /// _ => (), + /// } + /// ``` + pub MATCH_OVERLAPPING_ARM, + style, + "a `match` with overlapping arms" +} + +declare_clippy_lint! { + /// **What it does:** Checks for arm which matches all errors with `Err(_)` + /// and take drastic actions like `panic!`. + /// + /// **Why is this bad?** It is generally a bad practice, just like + /// catching all exceptions in java with `catch(Exception)` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: Result = Ok(3); + /// match x { + /// Ok(_) => println!("ok"), + /// Err(_) => panic!("err"), + /// } + /// ``` + pub MATCH_WILD_ERR_ARM, + style, + "a `match` with `Err(_)` arm and take drastic actions" +} + +declare_clippy_lint! { + /// **What it does:** Checks for match which is used to add a reference to an + /// `Option` value. + /// + /// **Why is this bad?** Using `as_ref()` or `as_mut()` instead is shorter. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: Option<()> = None; + /// let r: Option<&()> = match x { + /// None => None, + /// Some(ref v) => Some(v), + /// }; + /// ``` + pub MATCH_AS_REF, + complexity, + "a `match` on an Option value instead of using `as_ref()` or `as_mut`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard enum matches using `_`. + /// + /// **Why is this bad?** New enum variants added by library updates can be missed. + /// + /// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some + /// variants, and also may not use correct path to enum if it's not present in the current scope. + /// + /// **Example:** + /// ```rust + /// # enum Foo { A(usize), B(usize) } + /// # let x = Foo::B(1); + /// match x { + /// A => {}, + /// _ => {}, + /// } + /// ``` + pub WILDCARD_ENUM_MATCH_ARM, + restriction, + "a wildcard enum match arm using `_`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm. + /// + /// **Why is this bad?** Wildcard pattern already covers any other pattern as it will match anyway. + /// It makes the code less readable, especially to spot wildcard pattern use in match arm. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// match "foo" { + /// "a" => {}, + /// "bar" | _ => {}, + /// } + /// ``` + pub WILDCARD_IN_OR_PATTERNS, + complexity, + "a wildcard pattern used with others patterns in same match arm" +} + +declare_clippy_lint! { + /// **What it does:** Checks for matches being used to destructure a single-variant enum + /// or tuple struct where a `let` will suffice. + /// + /// **Why is this bad?** Just readability – `let` doesn't nest, whereas a `match` does. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// + /// let data = match wrapper { + /// Wrapper::Data(i) => i, + /// }; + /// ``` + /// + /// The correct use would be: + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// let Wrapper::Data(data) = wrapper; + /// ``` + pub INFALLIBLE_DESTRUCTURING_MATCH, + style, + "a `match` statement with a single infallible arm instead of a `let`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for useless match that binds to only one value. + /// + /// **Why is this bad?** Readability and needless complexity. + /// + /// **Known problems:** Suggested replacements may be incorrect when `match` + /// is actually binding temporary value, bringing a 'dropped while borrowed' error. + /// + /// **Example:** + /// ```rust + /// # let a = 1; + /// # let b = 2; + /// + /// // Bad + /// match (a, b) { + /// (c, d) => { + /// // useless match + /// } + /// } + /// + /// // Good + /// let (c, d) = (a, b); + /// ``` + pub MATCH_SINGLE_BINDING, + complexity, + "a match with a single binding instead of using `let` statement" +} + +declare_clippy_lint! { + /// **What it does:** Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. + /// + /// **Why is this bad?** Correctness and readability. It's like having a wildcard pattern after + /// matching all enum variants explicitly. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct A { a: i32 } + /// let a = A { a: 5 }; + /// + /// // Bad + /// match a { + /// A { a: 5, .. } => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match a { + /// A { a: 5 } => {}, + /// _ => {}, + /// } + /// ``` + pub REST_PAT_IN_FULLY_BOUND_STRUCTS, + restriction, + "a match on a struct that binds all fields but still uses the wildcard pattern" +} + +#[derive(Default)] +pub struct Matches { + infallible_destructuring_match_linted: bool, +} + +impl_lint_pass!(Matches => [ + SINGLE_MATCH, + MATCH_REF_PATS, + MATCH_BOOL, + SINGLE_MATCH_ELSE, + MATCH_OVERLAPPING_ARM, + MATCH_WILD_ERR_ARM, + MATCH_AS_REF, + WILDCARD_ENUM_MATCH_ARM, + WILDCARD_IN_OR_PATTERNS, + MATCH_SINGLE_BINDING, + INFALLIBLE_DESTRUCTURING_MATCH, + REST_PAT_IN_FULLY_BOUND_STRUCTS +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.kind { + check_single_match(cx, ex, arms, expr); + check_match_bool(cx, ex, arms, expr); + check_overlapping_arms(cx, ex, arms); + check_wild_err_arm(cx, ex, arms); + check_wild_enum_match(cx, ex, arms); + check_match_as_ref(cx, ex, arms, expr); + check_wild_in_or_pats(cx, arms); + + if self.infallible_destructuring_match_linted { + self.infallible_destructuring_match_linted = false; + } else { + check_match_single_binding(cx, ex, arms, expr); + } + } + if let ExprKind::Match(ref ex, ref arms, _) = expr.kind { + check_match_ref_pats(cx, ex, arms, expr); + } + } + + fn check_local(&mut self, cx: &LateContext<'a, 'tcx>, local: &'tcx Local<'_>) { + if_chain! { + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); + if let Some(ref expr) = local.init; + if let ExprKind::Match(ref target, ref arms, MatchSource::Normal) = expr.kind; + if arms.len() == 1 && arms[0].guard.is_none(); + if let PatKind::TupleStruct( + QPath::Resolved(None, ref variant_name), ref args, _) = arms[0].pat.kind; + if args.len() == 1; + if let Some(arg) = get_arg_name(&args[0]); + let body = remove_blocks(&arms[0].body); + if match_var(body, arg); + + then { + let mut applicability = Applicability::MachineApplicable; + self.infallible_destructuring_match_linted = true; + span_lint_and_sugg( + cx, + INFALLIBLE_DESTRUCTURING_MATCH, + local.span, + "you seem to be trying to use `match` to destructure a single infallible pattern. \ + Consider using `let`", + "try this", + format!( + "let {}({}) = {};", + snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), + snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), + snippet_with_applicability(cx, target.span, "..", &mut applicability), + ), + applicability, + ); + } + } + } + + fn check_pat(&mut self, cx: &LateContext<'a, 'tcx>, pat: &'tcx Pat<'_>) { + if_chain! { + if !in_external_macro(cx.sess(), pat.span); + if !in_macro(pat.span); + if let PatKind::Struct(ref qpath, fields, true) = pat.kind; + if let QPath::Resolved(_, ref path) = qpath; + if let Some(def_id) = path.res.opt_def_id(); + let ty = cx.tcx.type_of(def_id); + if let ty::Adt(def, _) = ty.kind; + if def.is_struct() || def.is_union(); + if fields.len() == def.non_enum_variant().fields.len(); + + then { + span_lint_and_help( + cx, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + pat.span, + "unnecessary use of `..` pattern in struct binding. All fields were already bound", + None, + "consider removing `..` from this binding", + ); + } + } + } +} + +#[rustfmt::skip] +fn check_single_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + if in_macro(expr.span) { + // Don't lint match expressions present in + // macro_rules! block + return; + } + if let PatKind::Or(..) = arms[0].pat.kind { + // don't lint for or patterns for now, this makes + // the lint noisy in unnecessary situations + return; + } + let els = remove_blocks(&arms[1].body); + let els = if is_unit_expr(els) { + None + } else if let ExprKind::Block(_, _) = els.kind { + // matches with blocks that contain statements are prettier as `if let + else` + Some(els) + } else { + // allow match arms with just expressions + return; + }; + let ty = cx.tables.expr_ty(ex); + if ty.kind != ty::Bool || is_allowed(cx, MATCH_BOOL, ex.hir_id) { + check_single_match_single_pattern(cx, ex, arms, expr, els); + check_single_match_opt_like(cx, ex, arms, expr, ty, els); + } + } +} + +fn check_single_match_single_pattern( + cx: &LateContext<'_, '_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + if is_wild(&arms[1].pat) { + report_single_match_single_pattern(cx, ex, arms, expr, els); + } +} + +fn report_single_match_single_pattern( + cx: &LateContext<'_, '_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; + let els_str = els.map_or(String::new(), |els| { + format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) + }); + span_lint_and_sugg( + cx, + lint, + expr.span, + "you seem to be trying to use match for destructuring a single pattern. Consider using `if \ + let`", + "try this", + format!( + "if let {} = {} {}{}", + snippet(cx, arms[0].pat.span, ".."), + snippet(cx, ex.span, ".."), + expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), + els_str, + ), + Applicability::HasPlaceholders, + ); +} + +fn check_single_match_opt_like( + cx: &LateContext<'_, '_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + ty: Ty<'_>, + els: Option<&Expr<'_>>, +) { + // list of candidate `Enum`s we know will never get any more members + let candidates = &[ + (&paths::COW, "Borrowed"), + (&paths::COW, "Cow::Borrowed"), + (&paths::COW, "Cow::Owned"), + (&paths::COW, "Owned"), + (&paths::OPTION, "None"), + (&paths::RESULT, "Err"), + (&paths::RESULT, "Ok"), + ]; + + let path = match arms[1].pat.kind { + PatKind::TupleStruct(ref path, ref inner, _) => { + // Contains any non wildcard patterns (e.g., `Err(err)`)? + if !inner.iter().all(is_wild) { + return; + } + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + }, + PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(), + PatKind::Path(ref path) => { + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + }, + _ => return, + }; + + for &(ty_path, pat_path) in candidates { + if path == *pat_path && match_type(cx, ty, ty_path) { + report_single_match_single_pattern(cx, ex, arms, expr, els); + } + } +} + +fn check_match_bool(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + // Type of expression is `bool`. + if cx.tables.expr_ty(ex).kind == ty::Bool { + span_lint_and_then( + cx, + MATCH_BOOL, + expr.span, + "you seem to be trying to match on a boolean expression", + move |diag| { + if arms.len() == 2 { + // no guards + let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pat.kind { + if let ExprKind::Lit(ref lit) = arm_bool.kind { + match lit.node { + LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), + LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), + _ => None, + } + } else { + None + } + } else { + None + }; + + if let Some((true_expr, false_expr)) = exprs { + let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { + (false, false) => Some(format!( + "if {} {} else {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)), + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )), + (false, true) => Some(format!( + "if {} {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)) + )), + (true, false) => { + let test = Sugg::hir(cx, ex, ".."); + Some(format!( + "if {} {}", + !test, + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )) + }, + (true, true) => None, + }; + + if let Some(sugg) = sugg { + diag.span_suggestion( + expr.span, + "consider using an `if`/`else` expression", + sugg, + Applicability::HasPlaceholders, + ); + } + } + } + }, + ); + } +} + +fn check_overlapping_arms<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { + if arms.len() >= 2 && cx.tables.expr_ty(ex).is_integral() { + let ranges = all_ranges(cx, arms, cx.tables.expr_ty(ex)); + let type_ranges = type_ranges(&ranges); + if !type_ranges.is_empty() { + if let Some((start, end)) = overlapping(&type_ranges) { + span_lint_and_note( + cx, + MATCH_OVERLAPPING_ARM, + start.span, + "some ranges overlap", + Some(end.span), + "overlaps with this", + ); + } + } + } +} + +fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { + let ex_ty = walk_ptrs_ty(cx.tables.expr_ty(ex)); + if is_type_diagnostic_item(cx, ex_ty, sym!(result_type)) { + for arm in arms { + if let PatKind::TupleStruct(ref path, ref inner, _) = arm.pat.kind { + let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); + if path_str == "Err" { + let mut matching_wild = inner.iter().any(is_wild); + let mut ident_bind_name = String::from("_"); + if !matching_wild { + // Looking for unused bindings (i.e.: `_e`) + inner.iter().for_each(|pat| { + if let PatKind::Binding(.., ident, None) = &pat.kind { + if ident.as_str().starts_with('_') && is_unused(ident, arm.body) { + ident_bind_name = (&ident.name.as_str()).to_string(); + matching_wild = true; + } + } + }); + } + if_chain! { + if matching_wild; + if let ExprKind::Block(ref block, _) = arm.body.kind; + if is_panic_block(block); + then { + // `Err(_)` or `Err(_e)` arm with `panic!` found + span_lint_and_note(cx, + MATCH_WILD_ERR_ARM, + arm.pat.span, + &format!("`Err({})` matches all errors", &ident_bind_name), + None, + "match each error separately or use the error output", + ); + } + } + } + } + } + } +} + +fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { + let ty = cx.tables.expr_ty(ex); + if !ty.is_enum() { + // If there isn't a nice closed set of possible values that can be conveniently enumerated, + // don't complain about not enumerating the mall. + return; + } + + // First pass - check for violation, but don't do much book-keeping because this is hopefully + // the uncommon case, and the book-keeping is slightly expensive. + let mut wildcard_span = None; + let mut wildcard_ident = None; + for arm in arms { + if let PatKind::Wild = arm.pat.kind { + wildcard_span = Some(arm.pat.span); + } else if let PatKind::Binding(_, _, ident, None) = arm.pat.kind { + wildcard_span = Some(arm.pat.span); + wildcard_ident = Some(ident); + } + } + + if let Some(wildcard_span) = wildcard_span { + // Accumulate the variants which should be put in place of the wildcard because they're not + // already covered. + + let mut missing_variants = vec![]; + if let ty::Adt(def, _) = ty.kind { + for variant in &def.variants { + missing_variants.push(variant); + } + } + + for arm in arms { + if arm.guard.is_some() { + // Guards mean that this case probably isn't exhaustively covered. Technically + // this is incorrect, as we should really check whether each variant is exhaustively + // covered by the set of guards that cover it, but that's really hard to do. + continue; + } + if let PatKind::Path(ref path) = arm.pat.kind { + if let QPath::Resolved(_, p) = path { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } + } else if let PatKind::TupleStruct(ref path, ..) = arm.pat.kind { + if let QPath::Resolved(_, p) = path { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } + } + } + + let mut suggestion: Vec = missing_variants + .iter() + .map(|v| { + let suffix = match v.ctor_kind { + CtorKind::Fn => "(..)", + CtorKind::Const | CtorKind::Fictive => "", + }; + let ident_str = if let Some(ident) = wildcard_ident { + format!("{} @ ", ident.name) + } else { + String::new() + }; + // This path assumes that the enum type is imported into scope. + format!("{}{}{}", ident_str, cx.tcx.def_path_str(v.def_id), suffix) + }) + .collect(); + + if suggestion.is_empty() { + return; + } + + let mut message = "wildcard match will miss any future added variants"; + + if let ty::Adt(def, _) = ty.kind { + if def.is_variant_list_non_exhaustive() { + message = "match on non-exhaustive enum doesn't explicitly match all known variants"; + suggestion.push(String::from("_")); + } + } + + span_lint_and_sugg( + cx, + WILDCARD_ENUM_MATCH_ARM, + wildcard_span, + message, + "try this", + suggestion.join(" | "), + Applicability::MachineApplicable, + ) + } +} + +// If the block contains only a `panic!` macro (as expression or statement) +fn is_panic_block(block: &Block<'_>) -> bool { + match (&block.expr, block.stmts.len(), block.stmts.first()) { + (&Some(ref exp), 0, _) => { + is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none() + }, + (&None, 1, Some(stmt)) => { + is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none() + }, + _ => false, + } +} + +fn check_match_ref_pats(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if has_only_ref_pats(arms) { + let mut suggs = Vec::with_capacity(arms.len() + 1); + let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind { + let span = ex.span.source_callsite(); + suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); + ( + "you don't need to add `&` to both the expression and the patterns", + "try", + ) + } else { + let span = ex.span.source_callsite(); + suggs.push((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); + ( + "you don't need to add `&` to all patterns", + "instead of prefixing all patterns with `&`, you can dereference the expression", + ) + }; + + suggs.extend(arms.iter().filter_map(|a| { + if let PatKind::Ref(ref refp, _) = a.pat.kind { + Some((a.pat.span, snippet(cx, refp.span, "..").to_string())) + } else { + None + } + })); + + span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { + if !expr.span.from_expansion() { + multispan_sugg(diag, msg.to_owned(), suggs); + } + }); + } +} + +fn check_match_as_ref(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + let arm_ref: Option = if is_none_arm(&arms[0]) { + is_ref_some_arm(&arms[1]) + } else if is_none_arm(&arms[1]) { + is_ref_some_arm(&arms[0]) + } else { + None + }; + if let Some(rb) = arm_ref { + let suggestion = if rb == BindingAnnotation::Ref { + "as_ref" + } else { + "as_mut" + }; + + let output_ty = cx.tables.expr_ty(expr); + let input_ty = cx.tables.expr_ty(ex); + + let cast = if_chain! { + if let ty::Adt(_, substs) = input_ty.kind; + let input_ty = substs.type_at(0); + if let ty::Adt(_, substs) = output_ty.kind; + let output_ty = substs.type_at(0); + if let ty::Ref(_, output_ty, _) = output_ty.kind; + if input_ty != output_ty; + then { + ".map(|x| x as _)" + } else { + "" + } + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MATCH_AS_REF, + expr.span, + &format!("use `{}()` instead", suggestion), + "try this", + format!( + "{}.{}(){}", + snippet_with_applicability(cx, ex.span, "_", &mut applicability), + suggestion, + cast, + ), + applicability, + ) + } + } +} + +fn check_wild_in_or_pats(cx: &LateContext<'_, '_>, arms: &[Arm<'_>]) { + for arm in arms { + if let PatKind::Or(ref fields) = arm.pat.kind { + // look for multiple fields in this arm that contains at least one Wild pattern + if fields.len() > 1 && fields.iter().any(is_wild) { + span_lint_and_help( + cx, + WILDCARD_IN_OR_PATTERNS, + arm.pat.span, + "wildcard pattern covers any other pattern as it will match anyway.", + None, + "Consider handling `_` separately.", + ); + } + } + } +} + +fn check_match_single_binding<'a>(cx: &LateContext<'_, 'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) { + return; + } + let matched_vars = ex.span; + let bind_names = arms[0].pat.span; + let match_body = remove_blocks(&arms[0].body); + let mut snippet_body = if match_body.span.from_expansion() { + Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() + } else { + snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() + }; + + // Do we need to add ';' to suggestion ? + match match_body.kind { + ExprKind::Block(block, _) => { + // macro + expr_ty(body) == () + if block.span.from_expansion() && cx.tables.expr_ty(&match_body).is_unit() { + snippet_body.push(';'); + } + }, + _ => { + // expr_ty(body) == () + if cx.tables.expr_ty(&match_body).is_unit() { + snippet_body.push(';'); + } + }, + } + + let mut applicability = Applicability::MaybeIncorrect; + match arms[0].pat.kind { + PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { + // If this match is in a local (`let`) stmt + let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) { + ( + parent_let_node.span, + format!( + "let {} = {};\n{}let {} = {};", + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), + snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability), + snippet_body + ), + ) + } else { + // If we are in closure, we need curly braces around suggestion + let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); + let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string()); + if let Some(parent_expr) = get_parent_expr(cx, expr) { + if let ExprKind::Closure(..) = parent_expr.kind { + cbrace_end = format!("\n{}}}", indent); + // Fix body indent due to the closure + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{}", indent); + } + }; + ( + expr.span, + format!( + "{}let {} = {};\n{}{}{}", + cbrace_start, + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + indent, + snippet_body, + cbrace_end + ), + ) + }; + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + target_span, + "this match could be written as a `let` statement", + "consider using `let` statement", + sugg, + applicability, + ); + }, + PatKind::Wild => { + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its body itself", + "consider using the match body instead", + snippet_body, + Applicability::MachineApplicable, + ); + }, + _ => (), + } +} + +/// Returns true if the `ex` match expression is in a local (`let`) statement +fn opt_parent_let<'a>(cx: &LateContext<'_, 'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> { + if_chain! { + let map = &cx.tcx.hir(); + if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)); + if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id)); + then { + return Some(parent_let_expr); + } + } + None +} + +/// Gets all arms that are unbounded `PatRange`s. +fn all_ranges<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + arms: &'tcx [Arm<'_>], + ty: Ty<'tcx>, +) -> Vec> { + arms.iter() + .flat_map(|arm| { + if let Arm { + ref pat, guard: None, .. + } = *arm + { + if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { + let lhs = match lhs { + Some(lhs) => constant(cx, cx.tables, lhs)?.0, + None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, + }; + let rhs = match rhs { + Some(rhs) => constant(cx, cx.tables, rhs)?.0, + None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, + }; + let rhs = match range_end { + RangeEnd::Included => Bound::Included(rhs), + RangeEnd::Excluded => Bound::Excluded(rhs), + }; + return Some(SpannedRange { + span: pat.span, + node: (lhs, rhs), + }); + } + + if let PatKind::Lit(ref value) = pat.kind { + let value = constant(cx, cx.tables, value)?.0; + return Some(SpannedRange { + span: pat.span, + node: (value.clone(), Bound::Included(value)), + }); + } + } + None + }) + .collect() +} + +#[derive(Debug, Eq, PartialEq)] +pub struct SpannedRange { + pub span: Span, + pub node: (T, Bound), +} + +type TypedRanges = Vec>; + +/// Gets all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway +/// and other types than +/// `Uint` and `Int` probably don't make sense. +fn type_ranges(ranges: &[SpannedRange]) -> TypedRanges { + ranges + .iter() + .filter_map(|range| match range.node { + (Constant::Int(start), Bound::Included(Constant::Int(end))) => Some(SpannedRange { + span: range.span, + node: (start, Bound::Included(end)), + }), + (Constant::Int(start), Bound::Excluded(Constant::Int(end))) => Some(SpannedRange { + span: range.span, + node: (start, Bound::Excluded(end)), + }), + (Constant::Int(start), Bound::Unbounded) => Some(SpannedRange { + span: range.span, + node: (start, Bound::Unbounded), + }), + _ => None, + }) + .collect() +} + +fn is_unit_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Tup(ref v) if v.is_empty() => true, + ExprKind::Block(ref b, _) if b.stmts.is_empty() && b.expr.is_none() => true, + _ => false, + } +} + +// Checks if arm has the form `None => None` +fn is_none_arm(arm: &Arm<'_>) -> bool { + match arm.pat.kind { + PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => true, + _ => false, + } +} + +// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) +fn is_ref_some_arm(arm: &Arm<'_>) -> Option { + if_chain! { + if let PatKind::TupleStruct(ref path, ref pats, _) = arm.pat.kind; + if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME); + if let PatKind::Binding(rb, .., ident, _) = pats[0].kind; + if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; + if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind; + if let ExprKind::Path(ref some_path) = e.kind; + if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1; + if let ExprKind::Path(ref qpath) = args[0].kind; + if let &QPath::Resolved(_, ref path2) = qpath; + if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; + then { + return Some(rb) + } + } + None +} + +fn has_only_ref_pats(arms: &[Arm<'_>]) -> bool { + let mapped = arms + .iter() + .map(|a| { + match a.pat.kind { + PatKind::Ref(..) => Some(true), // &-patterns + PatKind::Wild => Some(false), // an "anything" wildcard is also fine + _ => None, // any other pattern is not fine + } + }) + .collect::>>(); + // look for Some(v) where there's at least one true element + mapped.map_or(false, |v| v.iter().any(|el| *el)) +} + +pub fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> +where + T: Copy + Ord, +{ + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + enum Kind<'a, T> { + Start(T, &'a SpannedRange), + End(Bound, &'a SpannedRange), + } + + impl<'a, T: Copy> Kind<'a, T> { + fn range(&self) -> &'a SpannedRange { + match *self { + Kind::Start(_, r) | Kind::End(_, r) => r, + } + } + + fn value(self) -> Bound { + match self { + Kind::Start(t, _) => Bound::Included(t), + Kind::End(t, _) => t, + } + } + } + + impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl<'a, T: Copy + Ord> Ord for Kind<'a, T> { + fn cmp(&self, other: &Self) -> Ordering { + match (self.value(), other.value()) { + (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b), + // Range patterns cannot be unbounded (yet) + (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(), + (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) { + Ordering::Equal => Ordering::Greater, + other => other, + }, + (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) { + Ordering::Equal => Ordering::Less, + other => other, + }, + } + } + } + + let mut values = Vec::with_capacity(2 * ranges.len()); + + for r in ranges { + values.push(Kind::Start(r.node.0, r)); + values.push(Kind::End(r.node.1, r)); + } + + values.sort(); + + for (a, b) in values.iter().zip(values.iter().skip(1)) { + match (a, b) { + (&Kind::Start(_, ra), &Kind::End(_, rb)) => { + if ra.node != rb.node { + return Some((ra, rb)); + } + }, + (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (), + _ => return Some((a.range(), b.range())), + } + } + + None +} + +#[test] +fn test_overlapping() { + use rustc_span::source_map::DUMMY_SP; + + let sp = |s, e| SpannedRange { + span: DUMMY_SP, + node: (s, e), + }; + + assert_eq!(None, overlapping::(&[])); + assert_eq!(None, overlapping(&[sp(1, Bound::Included(4))])); + assert_eq!( + None, + overlapping(&[sp(1, Bound::Included(4)), sp(5, Bound::Included(6))]) + ); + assert_eq!( + None, + overlapping(&[ + sp(1, Bound::Included(4)), + sp(5, Bound::Included(6)), + sp(10, Bound::Included(11)) + ],) + ); + assert_eq!( + Some((&sp(1, Bound::Included(4)), &sp(3, Bound::Included(6)))), + overlapping(&[sp(1, Bound::Included(4)), sp(3, Bound::Included(6))]) + ); + assert_eq!( + Some((&sp(5, Bound::Included(6)), &sp(6, Bound::Included(11)))), + overlapping(&[ + sp(1, Bound::Included(4)), + sp(5, Bound::Included(6)), + sp(6, Bound::Included(11)) + ],) + ); +} diff --git a/src/tools/clippy/clippy_lints/src/mem_discriminant.rs b/src/tools/clippy/clippy_lints/src/mem_discriminant.rs new file mode 100644 index 0000000000000..3f953655670cf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mem_discriminant.rs @@ -0,0 +1,81 @@ +use crate::utils::{match_def_path, paths, snippet, span_lint_and_then, walk_ptrs_ty_depth}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use std::iter; + +declare_clippy_lint! { + /// **What it does:** Checks for calls of `mem::discriminant()` on a non-enum type. + /// + /// **Why is this bad?** The value of `mem::discriminant()` on non-enum types + /// is unspecified. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::mem; + /// + /// mem::discriminant(&"hello"); + /// mem::discriminant(&&Some(2)); + /// ``` + pub MEM_DISCRIMINANT_NON_ENUM, + correctness, + "calling `mem::descriminant` on non-enum type" +} + +declare_lint_pass!(MemDiscriminant => [MEM_DISCRIMINANT_NON_ENUM]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MemDiscriminant { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref func, ref func_args) = expr.kind; + // is `mem::discriminant` + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.tables.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::MEM_DISCRIMINANT); + // type is non-enum + let ty_param = cx.tables.node_substs(func.hir_id).type_at(0); + if !ty_param.is_enum(); + + then { + span_lint_and_then( + cx, + MEM_DISCRIMINANT_NON_ENUM, + expr.span, + &format!("calling `mem::discriminant` on non-enum type `{}`", ty_param), + |diag| { + // if this is a reference to an enum, suggest dereferencing + let (base_ty, ptr_depth) = walk_ptrs_ty_depth(ty_param); + if ptr_depth >= 1 && base_ty.is_enum() { + let param = &func_args[0]; + + // cancel out '&'s first + let mut derefs_needed = ptr_depth; + let mut cur_expr = param; + while derefs_needed > 0 { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref inner_expr) = cur_expr.kind { + derefs_needed -= 1; + cur_expr = inner_expr; + } else { + break; + } + } + + let derefs: String = iter::repeat('*').take(derefs_needed).collect(); + diag.span_suggestion( + param.span, + "try dereferencing", + format!("{}{}", derefs, snippet(cx, cur_expr.span, "")), + Applicability::MachineApplicable, + ); + } + }, + ) + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/mem_forget.rs b/src/tools/clippy/clippy_lints/src/mem_forget.rs new file mode 100644 index 0000000000000..c6ddc5de63b0e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mem_forget.rs @@ -0,0 +1,44 @@ +use crate::utils::{match_def_path, paths, qpath_res, span_lint}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `std::mem::forget(t)` where `t` is + /// `Drop`. + /// + /// **Why is this bad?** `std::mem::forget(t)` prevents `t` from running its + /// destructor, possibly causing leaks. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::mem; + /// # use std::rc::Rc; + /// mem::forget(Rc::new(55)) + /// ``` + pub MEM_FORGET, + restriction, + "`mem::forget` usage on `Drop` types, likely to cause memory leaks" +} + +declare_lint_pass!(MemForget => [MEM_FORGET]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MemForget { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Call(ref path_expr, ref args) = e.kind { + if let ExprKind::Path(ref qpath) = path_expr.kind { + if let Some(def_id) = qpath_res(cx, qpath, path_expr.hir_id).opt_def_id() { + if match_def_path(cx, def_id, &paths::MEM_FORGET) { + let forgot_ty = cx.tables.expr_ty(&args[0]); + + if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) { + span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type"); + } + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/mem_replace.rs b/src/tools/clippy/clippy_lints/src/mem_replace.rs new file mode 100644 index 0000000000000..ab6865bf0f3b7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mem_replace.rs @@ -0,0 +1,217 @@ +use crate::utils::{ + in_macro, match_def_path, match_qpath, paths, snippet, snippet_with_applicability, span_lint_and_help, + span_lint_and_sugg, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; + +declare_clippy_lint! { + /// **What it does:** Checks for `mem::replace()` on an `Option` with + /// `None`. + /// + /// **Why is this bad?** `Option` already has the method `take()` for + /// taking its current value (Some(..) or None) and replacing it with + /// `None`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::mem; + /// + /// let mut an_option = Some(0); + /// let replaced = mem::replace(&mut an_option, None); + /// ``` + /// Is better expressed with: + /// ```rust + /// let mut an_option = Some(0); + /// let taken = an_option.take(); + /// ``` + pub MEM_REPLACE_OPTION_WITH_NONE, + style, + "replacing an `Option` with `None` instead of `take()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `mem::replace(&mut _, mem::uninitialized())` + /// and `mem::replace(&mut _, mem::zeroed())`. + /// + /// **Why is this bad?** This will lead to undefined behavior even if the + /// value is overwritten later, because the uninitialized value may be + /// observed in the case of a panic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ``` + /// use std::mem; + ///# fn may_panic(v: Vec) -> Vec { v } + /// + /// #[allow(deprecated, invalid_value)] + /// fn myfunc (v: &mut Vec) { + /// let taken_v = unsafe { mem::replace(v, mem::uninitialized()) }; + /// let new_v = may_panic(taken_v); // undefined behavior on panic + /// mem::forget(mem::replace(v, new_v)); + /// } + /// ``` + /// + /// The [take_mut](https://docs.rs/take_mut) crate offers a sound solution, + /// at the cost of either lazily creating a replacement value or aborting + /// on panic, to ensure that the uninitialized value cannot be observed. + pub MEM_REPLACE_WITH_UNINIT, + correctness, + "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `std::mem::replace` on a value of type + /// `T` with `T::default()`. + /// + /// **Why is this bad?** `std::mem` module already has the method `take` to + /// take the current value and replace it with the default value of that type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut text = String::from("foo"); + /// let replaced = std::mem::replace(&mut text, String::default()); + /// ``` + /// Is better expressed with: + /// ```rust + /// let mut text = String::from("foo"); + /// let taken = std::mem::take(&mut text); + /// ``` + pub MEM_REPLACE_WITH_DEFAULT, + style, + "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`" +} + +declare_lint_pass!(MemReplace => + [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); + +fn check_replace_option_with_none(cx: &LateContext<'_, '_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { + if let ExprKind::Path(ref replacement_qpath) = src.kind { + // Check that second argument is `Option::None` + if match_qpath(replacement_qpath, &paths::OPTION_NONE) { + // Since this is a late pass (already type-checked), + // and we already know that the second argument is an + // `Option`, we do not need to check the first + // argument's type. All that's left is to get + // replacee's path. + let replaced_path = match dest.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, ref replaced) => { + if let ExprKind::Path(QPath::Resolved(None, ref replaced_path)) = replaced.kind { + replaced_path + } else { + return; + } + }, + ExprKind::Path(QPath::Resolved(None, ref replaced_path)) => replaced_path, + _ => return, + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MEM_REPLACE_OPTION_WITH_NONE, + expr_span, + "replacing an `Option` with `None`", + "consider `Option::take()` instead", + format!( + "{}.take()", + snippet_with_applicability(cx, replaced_path.span, "", &mut applicability) + ), + applicability, + ); + } + } +} + +fn check_replace_with_uninit(cx: &LateContext<'_, '_>, src: &Expr<'_>, expr_span: Span) { + if let ExprKind::Call(ref repl_func, ref repl_args) = src.kind { + if_chain! { + if repl_args.is_empty(); + if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; + if let Some(repl_def_id) = cx.tables.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); + then { + if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) { + span_lint_and_help( + cx, + MEM_REPLACE_WITH_UNINIT, + expr_span, + "replacing with `mem::uninitialized()`", + None, + "consider using the `take_mut` crate instead", + ); + } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) && + !cx.tables.expr_ty(src).is_primitive() { + span_lint_and_help( + cx, + MEM_REPLACE_WITH_UNINIT, + expr_span, + "replacing with `mem::zeroed()`", + None, + "consider using a default value or the `take_mut` crate instead", + ); + } + } + } + } +} + +fn check_replace_with_default(cx: &LateContext<'_, '_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { + if let ExprKind::Call(ref repl_func, _) = src.kind { + if_chain! { + if !in_external_macro(cx.tcx.sess, expr_span); + if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; + if let Some(repl_def_id) = cx.tables.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); + if match_def_path(cx, repl_def_id, &paths::DEFAULT_TRAIT_METHOD); + then { + span_lint_and_then( + cx, + MEM_REPLACE_WITH_DEFAULT, + expr_span, + "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`", + |diag| { + if !in_macro(expr_span) { + let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, "")); + + diag.span_suggestion( + expr_span, + "consider using", + suggestion, + Applicability::MachineApplicable + ); + } + } + ); + } + } + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MemReplace { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + // Check that `expr` is a call to `mem::replace()` + if let ExprKind::Call(ref func, ref func_args) = expr.kind; + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.tables.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::MEM_REPLACE); + if let [dest, src] = &**func_args; + then { + check_replace_option_with_none(cx, src, dest, expr.span); + check_replace_with_uninit(cx, src, expr.span); + check_replace_with_default(cx, src, dest, expr.span); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs b/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs new file mode 100644 index 0000000000000..06138ab9783c3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/inefficient_to_string.rs @@ -0,0 +1,62 @@ +use super::INEFFICIENT_TO_STRING; +use crate::utils::{ + is_type_diagnostic_item, match_def_path, paths, snippet_with_applicability, span_lint_and_then, walk_ptrs_ty_depth, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for the `INEFFICIENT_TO_STRING` lint +pub fn lint<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'tcx>) { + if_chain! { + if let Some(to_string_meth_did) = cx.tables.type_dependent_def_id(expr.hir_id); + if match_def_path(cx, to_string_meth_did, &paths::TO_STRING_METHOD); + if let Some(substs) = cx.tables.node_substs_opt(expr.hir_id); + let self_ty = substs.type_at(0); + let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty); + if deref_count >= 1; + if specializes_tostring(cx, deref_self_ty); + then { + span_lint_and_then( + cx, + INEFFICIENT_TO_STRING, + expr.span, + &format!("calling `to_string` on `{}`", arg_ty), + |diag| { + diag.help(&format!( + "`{}` implements `ToString` through a slower blanket impl, but `{}` has a fast specialization of `ToString`", + self_ty, deref_self_ty + )); + let mut applicability = Applicability::MachineApplicable; + let arg_snippet = snippet_with_applicability(cx, arg.span, "..", &mut applicability); + diag.span_suggestion( + expr.span, + "try dereferencing the receiver", + format!("({}{}).to_string()", "*".repeat(deref_count), arg_snippet), + applicability, + ); + }, + ); + } + } +} + +/// Returns whether `ty` specializes `ToString`. +/// Currently, these are `str`, `String`, and `Cow<'_, str>`. +fn specializes_tostring(cx: &LateContext<'_, '_>, ty: Ty<'_>) -> bool { + if let ty::Str = ty.kind { + return true; + } + + if is_type_diagnostic_item(cx, ty, sym!(string_type)) { + return true; + } + + if let ty::Adt(adt, substs) = ty.kind { + match_def_path(cx, adt.did, &paths::COW) && substs.type_at(1).is_str() + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs new file mode 100644 index 0000000000000..aaed6d75048c6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -0,0 +1,176 @@ +use crate::utils::{match_qpath, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_target::abi::LayoutOf; + +pub fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<'_>]], arith: &str) { + let unwrap_arg = &args[0][1]; + let arith_lhs = &args[1][0]; + let arith_rhs = &args[1][1]; + + let ty = cx.tables.expr_ty(arith_lhs); + if !ty.is_integral() { + return; + } + + let mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) { + mm + } else { + return; + }; + + if ty.is_signed() { + use self::{ + MinMax::{Max, Min}, + Sign::{Neg, Pos}, + }; + + let sign = if let Some(sign) = lit_sign(arith_rhs) { + sign + } else { + return; + }; + + match (arith, sign, mm) { + ("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (), + // "mul" is omitted because lhs can be negative. + _ => return, + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + super::MANUAL_SATURATING_ARITHMETIC, + expr.span, + "manual saturating arithmetic", + &format!("try using `saturating_{}`", arith), + format!( + "{}.saturating_{}({})", + snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), + arith, + snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), + ), + applicability, + ); + } else { + match (mm, arith) { + (MinMax::Max, "add") | (MinMax::Max, "mul") | (MinMax::Min, "sub") => (), + _ => return, + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + super::MANUAL_SATURATING_ARITHMETIC, + expr.span, + "manual saturating arithmetic", + &format!("try using `saturating_{}`", arith), + format!( + "{}.saturating_{}({})", + snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability), + arith, + snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability), + ), + applicability, + ); + } +} + +#[derive(PartialEq, Eq)] +enum MinMax { + Min, + Max, +} + +fn is_min_or_max<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &hir::Expr<'_>) -> Option { + // `T::max_value()` `T::min_value()` inherent methods + if_chain! { + if let hir::ExprKind::Call(func, args) = &expr.kind; + if args.is_empty(); + if let hir::ExprKind::Path(path) = &func.kind; + if let hir::QPath::TypeRelative(_, segment) = path; + then { + match &*segment.ident.as_str() { + "max_value" => return Some(MinMax::Max), + "min_value" => return Some(MinMax::Min), + _ => {} + } + } + } + + let ty = cx.tables.expr_ty(expr); + let ty_str = ty.to_string(); + + // `std::T::MAX` `std::T::MIN` constants + if let hir::ExprKind::Path(path) = &expr.kind { + if match_qpath(path, &["core", &ty_str, "MAX"][..]) { + return Some(MinMax::Max); + } + + if match_qpath(path, &["core", &ty_str, "MIN"][..]) { + return Some(MinMax::Min); + } + } + + // Literals + let bits = cx.layout_of(ty).unwrap().size.bits(); + let (minval, maxval): (u128, u128) = if ty.is_signed() { + let minval = 1 << (bits - 1); + let mut maxval = !(1 << (bits - 1)); + if bits != 128 { + maxval &= (1 << bits) - 1; + } + (minval, maxval) + } else { + (0, if bits == 128 { !0 } else { (1 << bits) - 1 }) + }; + + let check_lit = |expr: &hir::Expr<'_>, check_min: bool| { + if let hir::ExprKind::Lit(lit) = &expr.kind { + if let ast::LitKind::Int(value, _) = lit.node { + if value == maxval { + return Some(MinMax::Max); + } + + if check_min && value == minval { + return Some(MinMax::Min); + } + } + } + + None + }; + + if let r @ Some(_) = check_lit(expr, !ty.is_signed()) { + return r; + } + + if ty.is_signed() { + if let hir::ExprKind::Unary(hir::UnOp::UnNeg, val) = &expr.kind { + return check_lit(val, true); + } + } + + None +} + +#[derive(PartialEq, Eq)] +enum Sign { + Pos, + Neg, +} + +fn lit_sign(expr: &hir::Expr<'_>) -> Option { + if let hir::ExprKind::Unary(hir::UnOp::UnNeg, inner) = &expr.kind { + if let hir::ExprKind::Lit(..) = &inner.kind { + return Some(Sign::Neg); + } + } else if let hir::ExprKind::Lit(..) = &expr.kind { + return Some(Sign::Pos); + } + + None +} diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs new file mode 100644 index 0000000000000..3676dc5b09d21 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -0,0 +1,3657 @@ +mod inefficient_to_string; +mod manual_saturating_arithmetic; +mod option_map_unwrap_or; +mod unnecessary_filter_map; + +use std::borrow::Cow; +use std::fmt; +use std::iter; + +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::{self, Predicate, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::{sym, SymbolStr}; + +use crate::consts::{constant, Constant}; +use crate::utils::usage::mutated_variables; +use crate::utils::{ + get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy, + is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, + match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, + remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability, + snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, + span_lint_and_then, sugg, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for `.unwrap()` calls on `Option`s. + /// + /// **Why is this bad?** Usually it is better to handle the `None` case, or to + /// at least call `.expect(_)` with a more helpful message. Still, for a lot of + /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is + /// `Allow` by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Using unwrap on an `Option`: + /// + /// ```rust + /// let opt = Some(1); + /// opt.unwrap(); + /// ``` + /// + /// Better: + /// + /// ```rust + /// let opt = Some(1); + /// opt.expect("more helpful message"); + /// ``` + pub OPTION_UNWRAP_USED, + restriction, + "using `Option.unwrap()`, which should at least get a better message using `expect()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `.unwrap()` calls on `Result`s. + /// + /// **Why is this bad?** `result.unwrap()` will let the thread panic on `Err` + /// values. Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// Even if you want to panic on errors, not all `Error`s implement good + /// messages on display. Therefore, it may be beneficial to look at the places + /// where they may get displayed. Activate this lint to do just that. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Using unwrap on an `Result`: + /// + /// ```rust + /// let res: Result = Ok(1); + /// res.unwrap(); + /// ``` + /// + /// Better: + /// + /// ```rust + /// let res: Result = Ok(1); + /// res.expect("more helpful message"); + /// ``` + pub RESULT_UNWRAP_USED, + restriction, + "using `Result.unwrap()`, which might be better handled" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `.expect()` calls on `Option`s. + /// + /// **Why is this bad?** Usually it is better to handle the `None` case. Still, + /// for a lot of quick-and-dirty code, `expect` is a good choice, which is why + /// this lint is `Allow` by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Using expect on an `Option`: + /// + /// ```rust + /// let opt = Some(1); + /// opt.expect("one"); + /// ``` + /// + /// Better: + /// + /// ```rust,ignore + /// let opt = Some(1); + /// opt?; + /// ``` + pub OPTION_EXPECT_USED, + restriction, + "using `Option.expect()`, which might be better handled" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `.expect()` calls on `Result`s. + /// + /// **Why is this bad?** `result.expect()` will let the thread panic on `Err` + /// values. Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Using expect on an `Result`: + /// + /// ```rust + /// let res: Result = Ok(1); + /// res.expect("one"); + /// ``` + /// + /// Better: + /// + /// ```rust + /// let res: Result = Ok(1); + /// res?; + /// # Ok::<(), ()>(()) + /// ``` + pub RESULT_EXPECT_USED, + restriction, + "using `Result.expect()`, which might be better handled" +} + +declare_clippy_lint! { + /// **What it does:** Checks for methods that should live in a trait + /// implementation of a `std` trait (see [llogiq's blog + /// post](http://llogiq.github.io/2015/07/30/traits.html) for further + /// information) instead of an inherent implementation. + /// + /// **Why is this bad?** Implementing the traits improve ergonomics for users of + /// the code, often with very little cost. Also people seeing a `mul(...)` + /// method + /// may expect `*` to work equally, so you should have good reason to disappoint + /// them. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct X; + /// impl X { + /// fn add(&self, other: &X) -> X { + /// // .. + /// # X + /// } + /// } + /// ``` + pub SHOULD_IMPLEMENT_TRAIT, + style, + "defining a method that should be implementing a std trait" +} + +declare_clippy_lint! { + /// **What it does:** Checks for methods with certain name prefixes and which + /// doesn't match how self is taken. The actual rules are: + /// + /// |Prefix |`self` taken | + /// |-------|----------------------| + /// |`as_` |`&self` or `&mut self`| + /// |`from_`| none | + /// |`into_`|`self` | + /// |`is_` |`&self` or none | + /// |`to_` |`&self` | + /// + /// **Why is this bad?** Consistency breeds readability. If you follow the + /// conventions, your users won't be surprised that they, e.g., need to supply a + /// mutable reference to a `as_..` function. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct X; + /// impl X { + /// fn as_str(self) -> &'static str { + /// // .. + /// # "" + /// } + /// } + /// ``` + pub WRONG_SELF_CONVENTION, + style, + "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" +} + +declare_clippy_lint! { + /// **What it does:** This is the same as + /// [`wrong_self_convention`](#wrong_self_convention), but for public items. + /// + /// **Why is this bad?** See [`wrong_self_convention`](#wrong_self_convention). + /// + /// **Known problems:** Actually *renaming* the function may break clients if + /// the function is part of the public interface. In that case, be mindful of + /// the stability guarantees you've given your users. + /// + /// **Example:** + /// ```rust + /// # struct X; + /// impl<'a> X { + /// pub fn as_str(self) -> &'a str { + /// "foo" + /// } + /// } + /// ``` + pub WRONG_PUB_SELF_CONVENTION, + restriction, + "defining a public method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `ok().expect(..)`. + /// + /// **Why is this bad?** Because you usually call `expect()` on the `Result` + /// directly to get a better error message. + /// + /// **Known problems:** The error type needs to implement `Debug` + /// + /// **Example:** + /// ```rust + /// # let x = Ok::<_, ()>(()); + /// x.ok().expect("why did I do this again?") + /// ``` + pub OK_EXPECT, + style, + "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map(_).unwrap_or(_)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.map_or(_, _)`. + /// + /// **Known problems:** The order of the arguments is not in execution order + /// + /// **Example:** + /// ```rust + /// # let x = Some(1); + /// x.map(|a| a + 1).unwrap_or(0); + /// ``` + pub OPTION_MAP_UNWRAP_OR, + pedantic, + "using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map(_).unwrap_or_else(_)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.map_or_else(_, _)`. + /// + /// **Known problems:** The order of the arguments is not in execution order. + /// + /// **Example:** + /// ```rust + /// # let x = Some(1); + /// # fn some_function() -> usize { 1 } + /// x.map(|a| a + 1).unwrap_or_else(some_function); + /// ``` + pub OPTION_MAP_UNWRAP_OR_ELSE, + pedantic, + "using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `result.map(_).unwrap_or_else(_)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `result.map_or_else(_, _)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x: Result = Ok(1); + /// # fn some_function(foo: ()) -> usize { 1 } + /// x.map(|a| a + 1).unwrap_or_else(some_function); + /// ``` + pub RESULT_MAP_UNWRAP_OR_ELSE, + pedantic, + "using `Result.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `.map_or_else(g, f)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map_or(None, _)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.and_then(_)`. + /// + /// **Known problems:** The order of the arguments is not in execution order. + /// + /// **Example:** + /// ```rust + /// # let opt = Some(1); + /// opt.map_or(None, |a| Some(a + 1)) + /// # ; + /// ``` + pub OPTION_MAP_OR_NONE, + style, + "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map_or(None, Some)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.ok()`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// Bad: + /// ```rust + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.map_or(None, Some)); + /// ``` + /// + /// Good: + /// ```rust + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.ok()); + /// ``` + pub RESULT_MAP_OR_INTO_OPTION, + style, + "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.map(|x| y)`. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// let x = Some("foo"); + /// let _ = x.and_then(|s| Some(s.len())); + /// ``` + /// + /// The correct use would be: + /// + /// ```rust + /// let x = Some("foo"); + /// let _ = x.map(|s| s.len()); + /// ``` + pub OPTION_AND_THEN_SOME, + complexity, + "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter(_).next()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.find(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().filter(|x| **x == 0).next(); + /// ``` + /// Could be written as + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x == 0); + /// ``` + pub FILTER_NEXT, + complexity, + "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.skip_while(condition).next()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.find(!condition)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().skip_while(|x| **x == 0).next(); + /// ``` + /// Could be written as + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x != 0); + /// ``` + pub SKIP_WHILE_NEXT, + complexity, + "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.map(_).flatten(_)`, + /// + /// **Why is this bad?** Readability, this can be written more concisely as a + /// single method call. + /// + /// **Known problems:** + /// + /// **Example:** + /// ```rust + /// let vec = vec![vec![1]]; + /// vec.iter().map(|x| x.iter()).flatten(); + /// ``` + pub MAP_FLATTEN, + pedantic, + "using combinations of `flatten` and `map` which can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter(_).map(_)`, + /// `_.filter(_).flat_map(_)`, `_.filter_map(_).flat_map(_)` and similar. + /// + /// **Why is this bad?** Readability, this can be written more concisely as a + /// single method call. + /// + /// **Known problems:** Often requires a condition + Option/Iterator creation + /// inside the closure. + /// + /// **Example:** + /// ```rust + /// let vec = vec![1]; + /// vec.iter().filter(|x| **x == 0).map(|x| *x * 2); + /// ``` + pub FILTER_MAP, + pedantic, + "using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter_map(_).next()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as a + /// single method call. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next(); + /// ``` + /// Can be written as + /// + /// ```rust + /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None }); + /// ``` + pub FILTER_MAP_NEXT, + pedantic, + "using combination of `filter_map` and `next` which can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `flat_map(|x| x)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely by using `flatten`. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flat_map(|x| x); + /// ``` + /// Can be written as + /// ```rust + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flatten(); + /// ``` + pub FLAT_MAP_IDENTITY, + complexity, + "call to `flat_map` where `flatten` is sufficient" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.find(_).map(_)`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as a + /// single method call. + /// + /// **Known problems:** Often requires a condition + Option/Iterator creation + /// inside the closure. + /// + /// **Example:** + /// ```rust + /// (0..3).find(|x| *x == 2).map(|x| x * 2); + /// ``` + /// Can be written as + /// ```rust + /// (0..3).find_map(|x| if x == 2 { Some(x * 2) } else { None }); + /// ``` + pub FIND_MAP, + pedantic, + "using a combination of `find` and `map` can usually be written as a single method call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for an iterator search (such as `find()`, + /// `position()`, or `rposition()`) followed by a call to `is_some()`. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.any(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().find(|x| **x == 0).is_some(); + /// ``` + /// Could be written as + /// ```rust + /// # let vec = vec![1]; + /// vec.iter().any(|x| *x == 0); + /// ``` + pub SEARCH_IS_SOME, + complexity, + "using an iterator search followed by `is_some()`, which is more succinctly expressed as a call to `any()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.chars().next()` on a `str` to check + /// if it starts with a given char. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.starts_with(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let name = "foo"; + /// if name.chars().next() == Some('_') {}; + /// ``` + /// Could be written as + /// ```rust + /// let name = "foo"; + /// if name.starts_with('_') {}; + /// ``` + pub CHARS_NEXT_CMP, + style, + "using `.chars().next()` to check if a string starts with a char" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, + /// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or + /// `unwrap_or_default` instead. + /// + /// **Why is this bad?** The function will always be called and potentially + /// allocate an object acting as the default. + /// + /// **Known problems:** If the function has side-effects, not calling it will + /// change the semantic of the program, but you shouldn't rely on that anyway. + /// + /// **Example:** + /// ```rust + /// # let foo = Some(String::new()); + /// foo.unwrap_or(String::new()); + /// ``` + /// this can instead be written: + /// ```rust + /// # let foo = Some(String::new()); + /// foo.unwrap_or_else(String::new); + /// ``` + /// or + /// ```rust + /// # let foo = Some(String::new()); + /// foo.unwrap_or_default(); + /// ``` + pub OR_FUN_CALL, + perf, + "using any `*or` method with a function call, which suggests `*or_else`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, + /// etc., and suggests to use `unwrap_or_else` instead + /// + /// **Why is this bad?** The function will always be called. + /// + /// **Known problems:** If the function has side-effects, not calling it will + /// change the semantics of the program, but you shouldn't rely on that anyway. + /// + /// **Example:** + /// ```rust + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.expect(&format!("Err {}: {}", err_code, err_msg)); + /// ``` + /// or + /// ```rust + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str()); + /// ``` + /// this can instead be written: + /// ```rust + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg)); + /// ``` + pub EXPECT_FUN_CALL, + perf, + "using any `expect` method with a function call" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.clone()` on a `Copy` type. + /// + /// **Why is this bad?** The only reason `Copy` types implement `Clone` is for + /// generics, not for using the `clone` method on a concrete type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// 42u64.clone(); + /// ``` + pub CLONE_ON_COPY, + complexity, + "using `clone` on a `Copy` type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.clone()` on a ref-counted pointer, + /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified + /// function syntax instead (e.g., `Rc::clone(foo)`). + /// + /// **Why is this bad?** Calling '.clone()' on an Rc, Arc, or Weak + /// can obscure the fact that only the pointer is being cloned, not the underlying + /// data. + /// + /// **Example:** + /// ```rust + /// # use std::rc::Rc; + /// let x = Rc::new(1); + /// x.clone(); + /// ``` + pub CLONE_ON_REF_PTR, + restriction, + "using 'clone' on a ref-counted pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.clone()` on an `&&T`. + /// + /// **Why is this bad?** Cloning an `&&T` copies the inner `&T`, instead of + /// cloning the underlying `T`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn main() { + /// let x = vec![1]; + /// let y = &&x; + /// let z = y.clone(); + /// println!("{:p} {:p}", *y, z); // prints out the same pointer + /// } + /// ``` + pub CLONE_DOUBLE_REF, + correctness, + "using `clone` on `&&T`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.to_string()` on an `&&T` where + /// `T` implements `ToString` directly (like `&&str` or `&&String`). + /// + /// **Why is this bad?** This bypasses the specialized implementation of + /// `ToString` and instead goes through the more expensive string formatting + /// facilities. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Generic implementation for `T: Display` is used (slow) + /// ["foo", "bar"].iter().map(|s| s.to_string()); + /// + /// // OK, the specialized impl is used + /// ["foo", "bar"].iter().map(|&s| s.to_string()); + /// ``` + pub INEFFICIENT_TO_STRING, + pedantic, + "using `to_string` on `&&T` where `T: ToString`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `new` not returning a type that contains `Self`. + /// + /// **Why is this bad?** As a convention, `new` methods are used to make a new + /// instance of a type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct Foo; + /// # struct NotAFoo; + /// impl Foo { + /// fn new() -> NotAFoo { + /// # NotAFoo + /// } + /// } + /// ``` + /// + /// ```rust + /// # struct Foo; + /// # struct FooError; + /// impl Foo { + /// // Good. Return type contains `Self` + /// fn new() -> Result { + /// # Ok(Foo) + /// } + /// } + /// ``` + /// + /// ```rust + /// # struct Foo; + /// struct Bar(Foo); + /// impl Foo { + /// // Bad. The type name must contain `Self`. + /// fn new() -> Bar { + /// # Bar(Foo) + /// } + /// } + /// ``` + pub NEW_RET_NO_SELF, + style, + "not returning type containing `Self` in a `new` method" +} + +declare_clippy_lint! { + /// **What it does:** Checks for string methods that receive a single-character + /// `str` as an argument, e.g., `_.split("x")`. + /// + /// **Why is this bad?** Performing these methods using a `char` is faster than + /// using a `str`. + /// + /// **Known problems:** Does not catch multi-byte unicode characters. + /// + /// **Example:** + /// `_.split("x")` could be `_.split('x')` + pub SINGLE_CHAR_PATTERN, + perf, + "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for getting the inner pointer of a temporary + /// `CString`. + /// + /// **Why is this bad?** The inner pointer of a `CString` is only valid as long + /// as the `CString` is alive. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::ffi::CString; + /// # fn call_some_ffi_func(_: *const i8) {} + /// # + /// let c_str = CString::new("foo").unwrap().as_ptr(); + /// unsafe { + /// call_some_ffi_func(c_str); + /// } + /// ``` + /// Here `c_str` point to a freed address. The correct use would be: + /// ```rust + /// # use std::ffi::CString; + /// # fn call_some_ffi_func(_: *const i8) {} + /// # + /// let c_str = CString::new("foo").unwrap(); + /// unsafe { + /// call_some_ffi_func(c_str.as_ptr()); + /// } + /// ``` + pub TEMPORARY_CSTRING_AS_PTR, + correctness, + "getting the inner pointer of a temporary `CString`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calling `.step_by(0)` on iterators which panics. + /// + /// **Why is this bad?** This very much looks like an oversight. Use `panic!()` instead if you + /// actually intend to panic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,should_panic + /// for x in (0..100).step_by(0) { + /// //.. + /// } + /// ``` + pub ITERATOR_STEP_BY_ZERO, + correctness, + "using `Iterator::step_by(0)`, which will panic at runtime" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `iter.nth(0)`. + /// + /// **Why is this bad?** `iter.next()` is equivalent to + /// `iter.nth(0)`, as they both consume the next element, + /// but is more readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # use std::collections::HashSet; + /// // Bad + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().nth(0); + /// + /// // Good + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().next(); + /// ``` + pub ITER_NTH_ZERO, + style, + "replace `iter.nth(0)` with `iter.next()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `.iter().nth()` (and the related + /// `.iter_mut().nth()`) on standard library types with O(1) element access. + /// + /// **Why is this bad?** `.get()` and `.get_mut()` are more efficient and more + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + /// The correct use would be: + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.get(3); + /// let bad_slice = &some_vec[..].get(3); + /// ``` + pub ITER_NTH, + perf, + "using `.iter().nth()` on a standard library type with O(1) element access" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `.skip(x).next()` on iterators. + /// + /// **Why is this bad?** `.nth(x)` is cleaner + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().skip(3).next(); + /// let bad_slice = &some_vec[..].iter().skip(3).next(); + /// ``` + /// The correct use would be: + /// ```rust + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + pub ITER_SKIP_NEXT, + style, + "using `.skip(x).next()` on an iterator" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `.get().unwrap()` (or + /// `.get_mut().unwrap`) on a standard library type which implements `Index` + /// + /// **Why is this bad?** Using the Index trait (`[]`) is more clear and more + /// concise. + /// + /// **Known problems:** Not a replacement for error handling: Using either + /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic` + /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a + /// temporary placeholder for dealing with the `Option` type, then this does + /// not mitigate the need for error handling. If there is a chance that `.get()` + /// will be `None` in your program, then it is advisable that the `None` case + /// is handled in a future refactor instead of using `.unwrap()` or the Index + /// trait. + /// + /// **Example:** + /// ```rust + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec.get(3).unwrap(); + /// *some_vec.get_mut(0).unwrap() = 1; + /// ``` + /// The correct use would be: + /// ```rust + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec[3]; + /// some_vec[0] = 1; + /// ``` + pub GET_UNWRAP, + restriction, + "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.extend(s.chars())` where s is a + /// `&str` or `String`. + /// + /// **Why is this bad?** `.push_str(s)` is clearer + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.extend(abc.chars()); + /// s.extend(def.chars()); + /// ``` + /// The correct use would be: + /// ```rust + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.push_str(abc); + /// s.push_str(&def); + /// ``` + pub STRING_EXTEND_CHARS, + style, + "using `x.extend(s.chars())` where s is a `&str` or `String`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.cloned().collect()` on slice to + /// create a `Vec`. + /// + /// **Why is this bad?** `.to_vec()` is clearer + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s[..].iter().cloned().collect(); + /// ``` + /// The better use would be: + /// ```rust + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s.to_vec(); + /// ``` + pub ITER_CLONED_COLLECT, + style, + "using `.cloned().collect()` on slice to create a `Vec`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.chars().last()` or + /// `.chars().next_back()` on a `str` to check if it ends with a given char. + /// + /// **Why is this bad?** Readability, this can be written more concisely as + /// `_.ends_with(_)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let name = "_"; + /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-') + /// # ; + /// ``` + pub CHARS_LAST_CMP, + style, + "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `.as_ref()` or `.as_mut()` where the + /// types before and after the call are the same. + /// + /// **Why is this bad?** The call is unnecessary. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x.as_ref()); + /// ``` + /// The correct use would be: + /// ```rust + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x); + /// ``` + pub USELESS_ASREF, + complexity, + "using `as_ref` where the types before and after the call are the same" +} + +declare_clippy_lint! { + /// **What it does:** Checks for using `fold` when a more succinct alternative exists. + /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, + /// `sum` or `product`. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** False positive in pattern guards. Will be resolved once + /// non-lexical lifetimes are stable. + /// + /// **Example:** + /// ```rust + /// let _ = (0..3).fold(false, |acc, x| acc || x > 2); + /// ``` + /// This could be written as: + /// ```rust + /// let _ = (0..3).any(|x| x > 2); + /// ``` + pub UNNECESSARY_FOLD, + style, + "using `fold` when a more succinct alternative exists" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `filter_map` calls which could be replaced by `filter` or `map`. + /// More specifically it checks if the closure provided is only performing one of the + /// filter or map operations and suggests the appropriate option. + /// + /// **Why is this bad?** Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); + /// ``` + /// As there is no transformation of the argument this could be written as: + /// ```rust + /// let _ = (0..3).filter(|&x| x > 2); + /// ``` + /// + /// ```rust + /// let _ = (0..4).filter_map(|x| Some(x + 1)); + /// ``` + /// As there is no conditional check on the argument this could be written as: + /// ```rust + /// let _ = (0..4).map(|x| x + 1); + /// ``` + pub UNNECESSARY_FILTER_MAP, + complexity, + "using `filter_map` when a more succinct alternative exists" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `into_iter` calls on references which should be replaced by `iter` + /// or `iter_mut`. + /// + /// **Why is this bad?** Readability. Calling `into_iter` on a reference will not move out its + /// content into the resulting iterator, which is confusing. It is better just call `iter` or + /// `iter_mut` directly. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// let _ = (&vec![3, 4, 5]).into_iter(); + /// ``` + pub INTO_ITER_ON_REF, + style, + "using `.into_iter()` on a reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `map` followed by a `count`. + /// + /// **Why is this bad?** It looks suspicious. Maybe `map` was confused with `filter`. + /// If the `map` call is intentional, this should be rewritten. Or, if you intend to + /// drive the iterator to completion, you can just use `for_each` instead. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// let _ = (0..3).map(|x| x + 2).count(); + /// ``` + pub SUSPICIOUS_MAP, + complexity, + "suspicious usage of map" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `MaybeUninit::uninit().assume_init()`. + /// + /// **Why is this bad?** For most types, this is undefined behavior. + /// + /// **Known problems:** For now, we accept empty tuples and tuples / arrays + /// of `MaybeUninit`. There may be other types that allow uninitialized + /// data, but those are not yet rigorously defined. + /// + /// **Example:** + /// + /// ```rust + /// // Beware the UB + /// use std::mem::MaybeUninit; + /// + /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + /// ``` + /// + /// Note that the following is OK: + /// + /// ```rust + /// use std::mem::MaybeUninit; + /// + /// let _: [MaybeUninit; 5] = unsafe { + /// MaybeUninit::uninit().assume_init() + /// }; + /// ``` + pub UNINIT_ASSUMED_INIT, + correctness, + "`MaybeUninit::uninit().assume_init()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`. + /// + /// **Why is this bad?** These can be written simply with `saturating_add/sub` methods. + /// + /// **Example:** + /// + /// ```rust + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.checked_add(y).unwrap_or(u32::MAX); + /// let sub = x.checked_sub(y).unwrap_or(u32::MIN); + /// ``` + /// + /// can be written using dedicated methods for saturating addition/subtraction as: + /// + /// ```rust + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.saturating_add(y); + /// let sub = x.saturating_sub(y); + /// ``` + pub MANUAL_SATURATING_ARITHMETIC, + style, + "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to + /// zero-sized types + /// + /// **Why is this bad?** This is a no-op, and likely unintended + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// unsafe { (&() as *const ()).offset(1) }; + /// ``` + pub ZST_OFFSET, + correctness, + "Check for offset calculations on raw pointers to zero-sized types" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `FileType::is_file()`. + /// + /// **Why is this bad?** When people testing a file type with `FileType::is_file` + /// they are testing whether a path is something they can get bytes from. But + /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover + /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention. + /// + /// **Example:** + /// + /// ```rust + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if filetype.is_file() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + /// + /// should be written as: + /// + /// ```rust + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if !filetype.is_dir() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + pub FILETYPE_IS_FILE, + restriction, + "`FileType::is_file` is not recommended to test for readable file type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str). + /// + /// **Why is this bad?** Readability, this can be written more concisely as a + /// single method call. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let opt = Some("".to_string()); + /// opt.as_ref().map(String::as_str) + /// # ; + /// ``` + /// Can be written as + /// ```rust + /// # let opt = Some("".to_string()); + /// opt.as_deref() + /// # ; + /// ``` + pub OPTION_AS_REF_DEREF, + complexity, + "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" +} + +declare_lint_pass!(Methods => [ + OPTION_UNWRAP_USED, + RESULT_UNWRAP_USED, + OPTION_EXPECT_USED, + RESULT_EXPECT_USED, + SHOULD_IMPLEMENT_TRAIT, + WRONG_SELF_CONVENTION, + WRONG_PUB_SELF_CONVENTION, + OK_EXPECT, + OPTION_MAP_UNWRAP_OR, + OPTION_MAP_UNWRAP_OR_ELSE, + RESULT_MAP_UNWRAP_OR_ELSE, + RESULT_MAP_OR_INTO_OPTION, + OPTION_MAP_OR_NONE, + OPTION_AND_THEN_SOME, + OR_FUN_CALL, + EXPECT_FUN_CALL, + CHARS_NEXT_CMP, + CHARS_LAST_CMP, + CLONE_ON_COPY, + CLONE_ON_REF_PTR, + CLONE_DOUBLE_REF, + INEFFICIENT_TO_STRING, + NEW_RET_NO_SELF, + SINGLE_CHAR_PATTERN, + SEARCH_IS_SOME, + TEMPORARY_CSTRING_AS_PTR, + FILTER_NEXT, + SKIP_WHILE_NEXT, + FILTER_MAP, + FILTER_MAP_NEXT, + FLAT_MAP_IDENTITY, + FIND_MAP, + MAP_FLATTEN, + ITERATOR_STEP_BY_ZERO, + ITER_NTH, + ITER_NTH_ZERO, + ITER_SKIP_NEXT, + GET_UNWRAP, + STRING_EXTEND_CHARS, + ITER_CLONED_COLLECT, + USELESS_ASREF, + UNNECESSARY_FOLD, + UNNECESSARY_FILTER_MAP, + INTO_ITER_ON_REF, + SUSPICIOUS_MAP, + UNINIT_ASSUMED_INIT, + MANUAL_SATURATING_ARITHMETIC, + ZST_OFFSET, + FILETYPE_IS_FILE, + OPTION_AS_REF_DEREF, +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if in_macro(expr.span) { + return; + } + + let (method_names, arg_lists, method_spans) = method_calls(expr, 2); + let method_names: Vec = method_names.iter().map(|s| s.as_str()).collect(); + let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect(); + + match method_names.as_slice() { + ["unwrap", "get"] => lint_get_unwrap(cx, expr, arg_lists[1], false), + ["unwrap", "get_mut"] => lint_get_unwrap(cx, expr, arg_lists[1], true), + ["unwrap", ..] => lint_unwrap(cx, expr, arg_lists[0]), + ["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]), + ["expect", ..] => lint_expect(cx, expr, arg_lists[0]), + ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), + ["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]), + ["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]), + ["and_then", ..] => lint_option_and_then_some(cx, expr, arg_lists[0]), + ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), + ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), + ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), + ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), + ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]), + ["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0], method_spans[0]), + ["flatten", "map"] => lint_map_flatten(cx, expr, arg_lists[1]), + ["is_some", "find"] => lint_search_is_some(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]), + ["is_some", "position"] => { + lint_search_is_some(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1]) + }, + ["is_some", "rposition"] => { + lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1]) + }, + ["extend", ..] => lint_extend(cx, expr, arg_lists[0]), + ["as_ptr", "unwrap"] | ["as_ptr", "expect"] => { + lint_cstring_as_ptr(cx, expr, &arg_lists[1][0], &arg_lists[0][0]) + }, + ["nth", "iter"] => lint_iter_nth(cx, expr, &arg_lists, false), + ["nth", "iter_mut"] => lint_iter_nth(cx, expr, &arg_lists, true), + ["nth", ..] => lint_iter_nth_zero(cx, expr, arg_lists[0]), + ["step_by", ..] => lint_step_by(cx, expr, arg_lists[0]), + ["next", "skip"] => lint_iter_skip_next(cx, expr), + ["collect", "cloned"] => lint_iter_cloned_collect(cx, expr, arg_lists[1]), + ["as_ref"] => lint_asref(cx, expr, "as_ref", arg_lists[0]), + ["as_mut"] => lint_asref(cx, expr, "as_mut", arg_lists[0]), + ["fold", ..] => lint_unnecessary_fold(cx, expr, arg_lists[0], method_spans[0]), + ["filter_map", ..] => unnecessary_filter_map::lint(cx, expr, arg_lists[0]), + ["count", "map"] => lint_suspicious_map(cx, expr), + ["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr), + ["unwrap_or", arith @ "checked_add"] + | ["unwrap_or", arith @ "checked_sub"] + | ["unwrap_or", arith @ "checked_mul"] => { + manual_saturating_arithmetic::lint(cx, expr, &arg_lists, &arith["checked_".len()..]) + }, + ["add"] | ["offset"] | ["sub"] | ["wrapping_offset"] | ["wrapping_add"] | ["wrapping_sub"] => { + check_pointer_offset(cx, expr, arg_lists[0]) + }, + ["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]), + ["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false), + ["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true), + _ => {}, + } + + match expr.kind { + hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args) => { + lint_or_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args); + lint_expect_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args); + + let self_ty = cx.tables.expr_ty_adjusted(&args[0]); + if args.len() == 1 && method_call.ident.name == sym!(clone) { + lint_clone_on_copy(cx, expr, &args[0], self_ty); + lint_clone_on_ref_ptr(cx, expr, &args[0]); + } + if args.len() == 1 && method_call.ident.name == sym!(to_string) { + inefficient_to_string::lint(cx, expr, &args[0], self_ty); + } + + match self_ty.kind { + ty::Ref(_, ty, _) if ty.kind == ty::Str => { + for &(method, pos) in &PATTERN_METHODS { + if method_call.ident.name.as_str() == method && args.len() > pos { + lint_single_char_pattern(cx, expr, &args[pos]); + } + } + }, + ty::Ref(..) if method_call.ident.name == sym!(into_iter) => { + lint_into_iter(cx, expr, self_ty, *method_span); + }, + _ => (), + } + }, + hir::ExprKind::Binary(op, ref lhs, ref rhs) + if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => + { + let mut info = BinaryExprInfo { + expr, + chain: lhs, + other: rhs, + eq: op.node == hir::BinOpKind::Eq, + }; + lint_binary_expr_with_method_call(cx, &mut info); + } + _ => (), + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + if in_external_macro(cx.sess(), impl_item.span) { + return; + } + let name = impl_item.ident.name.as_str(); + let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id); + let item = cx.tcx.hir().expect_item(parent); + let def_id = cx.tcx.hir().local_def_id(item.hir_id); + let self_ty = cx.tcx.type_of(def_id); + if_chain! { + if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind; + if let Some(first_arg) = iter_input_pats(&sig.decl, cx.tcx.hir().body(id)).next(); + if let hir::ItemKind::Impl{ of_trait: None, .. } = item.kind; + + let method_def_id = cx.tcx.hir().local_def_id(impl_item.hir_id); + let method_sig = cx.tcx.fn_sig(method_def_id); + let method_sig = cx.tcx.erase_late_bound_regions(&method_sig); + + let first_arg_ty = &method_sig.inputs().iter().next(); + + // check conventions w.r.t. conversion method names and predicates + if let Some(first_arg_ty) = first_arg_ty; + + then { + if cx.access_levels.is_exported(impl_item.hir_id) { + // check missing trait implementations + for &(method_name, n_args, fn_header, self_kind, out_type, trait_name) in &TRAIT_METHODS { + if name == method_name && + sig.decl.inputs.len() == n_args && + out_type.matches(cx, &sig.decl.output) && + self_kind.matches(cx, self_ty, first_arg_ty) && + fn_header_equals(*fn_header, sig.header) { + span_lint(cx, SHOULD_IMPLEMENT_TRAIT, impl_item.span, &format!( + "defining a method called `{}` on this type; consider implementing \ + the `{}` trait or choosing a less ambiguous name", name, trait_name)); + } + } + } + + if let Some((ref conv, self_kinds)) = &CONVENTIONS + .iter() + .find(|(ref conv, _)| conv.check(&name)) + { + if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) { + let lint = if item.vis.node.is_pub() { + WRONG_PUB_SELF_CONVENTION + } else { + WRONG_SELF_CONVENTION + }; + + span_lint( + cx, + lint, + first_arg.pat.span, + &format!( + "methods called `{}` usually take {}; consider choosing a less \ + ambiguous name", + conv, + &self_kinds + .iter() + .map(|k| k.description()) + .collect::>() + .join(" or ") + ), + ); + } + } + } + } + + if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { + let ret_ty = return_ty(cx, impl_item.hir_id); + + let contains_self_ty = |ty: Ty<'tcx>| { + ty.walk().any(|inner| match inner.unpack() { + GenericArgKind::Type(inner_ty) => same_tys(cx, self_ty, inner_ty), + + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, + }) + }; + + // walk the return type and check for Self (this does not check associated types) + if contains_self_ty(ret_ty) { + return; + } + + // if return type is impl trait, check the associated types + if let ty::Opaque(def_id, _) = ret_ty.kind { + // one of the associated types must be Self + for predicate in cx.tcx.predicates_of(def_id).predicates { + match predicate { + (Predicate::Projection(poly_projection_predicate), _) => { + let binder = poly_projection_predicate.ty(); + let associated_type = binder.skip_binder(); + + // walk the associated type and check for Self + if contains_self_ty(associated_type) { + return; + } + }, + (_, _) => {}, + } + } + } + + if name == "new" && !same_tys(cx, ret_ty, self_ty) { + span_lint( + cx, + NEW_RET_NO_SELF, + impl_item.span, + "methods called `new` usually return `Self`", + ); + } + } + } +} + +/// Checks for the `OR_FUN_CALL` lint. +#[allow(clippy::too_many_lines)] +fn lint_or_fun_call<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &hir::Expr<'_>, + method_span: Span, + name: &str, + args: &'tcx [hir::Expr<'_>], +) { + // Searches an expression for method calls or function calls that aren't ctors + struct FunCallFinder<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + found: bool, + } + + impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + let call_found = match &expr.kind { + // ignore enum and struct constructors + hir::ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr), + hir::ExprKind::MethodCall(..) => true, + _ => false, + }; + + if call_found { + self.found |= true; + } + + if !self.found { + intravisit::walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } + } + + /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`. + fn check_unwrap_or_default( + cx: &LateContext<'_, '_>, + name: &str, + fun: &hir::Expr<'_>, + self_expr: &hir::Expr<'_>, + arg: &hir::Expr<'_>, + or_has_args: bool, + span: Span, + ) -> bool { + if_chain! { + if !or_has_args; + if name == "unwrap_or"; + if let hir::ExprKind::Path(ref qpath) = fun.kind; + let path = &*last_path_segment(qpath).ident.as_str(); + if ["default", "new"].contains(&path); + let arg_ty = cx.tables.expr_ty(arg); + if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); + if implements_trait(cx, arg_ty, default_trait_id, &[]); + + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span, + &format!("use of `{}` followed by a call to `{}`", name, path), + "try this", + format!( + "{}.unwrap_or_default()", + snippet_with_applicability(cx, self_expr.span, "_", &mut applicability) + ), + applicability, + ); + + true + } else { + false + } + } + } + + /// Checks for `*or(foo())`. + #[allow(clippy::too_many_arguments)] + fn check_general_case<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + name: &str, + method_span: Span, + fun_span: Span, + self_expr: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + or_has_args: bool, + span: Span, + ) { + // (path, fn_has_argument, methods, suffix) + let know_types: &[(&[_], _, &[_], _)] = &[ + (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), + (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"), + (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), + (&paths::RESULT, true, &["or", "unwrap_or"], "else"), + ]; + + if_chain! { + if know_types.iter().any(|k| k.2.contains(&name)); + + let mut finder = FunCallFinder { cx: &cx, found: false }; + if { finder.visit_expr(&arg); finder.found }; + if !contains_return(&arg); + + let self_ty = cx.tables.expr_ty(self_expr); + + if let Some(&(_, fn_has_arguments, poss, suffix)) = + know_types.iter().find(|&&i| match_type(cx, self_ty, i.0)); + + if poss.contains(&name); + + then { + let sugg: Cow<'_, _> = match (fn_has_arguments, !or_has_args) { + (true, _) => format!("|_| {}", snippet_with_macro_callsite(cx, arg.span, "..")).into(), + (false, false) => format!("|| {}", snippet_with_macro_callsite(cx, arg.span, "..")).into(), + (false, true) => snippet_with_macro_callsite(cx, fun_span, ".."), + }; + let span_replace_word = method_span.with_hi(span.hi()); + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("{}_{}({})", name, suffix, sugg), + Applicability::HasPlaceholders, + ); + } + } + } + + if args.len() == 2 { + match args[1].kind { + hir::ExprKind::Call(ref fun, ref or_args) => { + let or_has_args = !or_args.is_empty(); + if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) { + check_general_case( + cx, + name, + method_span, + fun.span, + &args[0], + &args[1], + or_has_args, + expr.span, + ); + } + }, + hir::ExprKind::MethodCall(_, span, ref or_args) => check_general_case( + cx, + name, + method_span, + span, + &args[0], + &args[1], + !or_args.is_empty(), + expr.span, + ), + _ => {}, + } + } +} + +/// Checks for the `EXPECT_FUN_CALL` lint. +#[allow(clippy::too_many_lines)] +fn lint_expect_fun_call( + cx: &LateContext<'_, '_>, + expr: &hir::Expr<'_>, + method_span: Span, + name: &str, + args: &[hir::Expr<'_>], +) { + // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or + // `&str` + fn get_arg_root<'a>(cx: &LateContext<'_, '_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { + let mut arg_root = arg; + loop { + arg_root = match &arg_root.kind { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, + hir::ExprKind::MethodCall(method_name, _, call_args) => { + if call_args.len() == 1 + && (method_name.ident.name == sym!(as_str) || method_name.ident.name == sym!(as_ref)) + && { + let arg_type = cx.tables.expr_ty(&call_args[0]); + let base_type = walk_ptrs_ty(arg_type); + base_type.kind == ty::Str || is_type_diagnostic_item(cx, base_type, sym!(string_type)) + } + { + &call_args[0] + } else { + break; + } + }, + _ => break, + }; + } + arg_root + } + + // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be + // converted to string. + fn requires_to_string(cx: &LateContext<'_, '_>, arg: &hir::Expr<'_>) -> bool { + let arg_ty = cx.tables.expr_ty(arg); + if is_type_diagnostic_item(cx, arg_ty, sym!(string_type)) { + return false; + } + if let ty::Ref(_, ty, ..) = arg_ty.kind { + if ty.kind == ty::Str && can_be_static_str(cx, arg) { + return false; + } + }; + true + } + + // Check if an expression could have type `&'static str`, knowing that it + // has type `&str` for some lifetime. + fn can_be_static_str(cx: &LateContext<'_, '_>, arg: &hir::Expr<'_>) -> bool { + match arg.kind { + hir::ExprKind::Lit(_) => true, + hir::ExprKind::Call(fun, _) => { + if let hir::ExprKind::Path(ref p) = fun.kind { + match cx.tables.qpath_res(p, fun.hir_id) { + hir::def::Res::Def(hir::def::DefKind::Fn, def_id) + | hir::def::Res::Def(hir::def::DefKind::AssocFn, def_id) => matches!( + cx.tcx.fn_sig(def_id).output().skip_binder().kind, + ty::Ref(ty::ReStatic, ..) + ), + _ => false, + } + } else { + false + } + }, + hir::ExprKind::MethodCall(..) => cx.tables.type_dependent_def_id(arg.hir_id).map_or(false, |method_id| { + matches!( + cx.tcx.fn_sig(method_id).output().skip_binder().kind, + ty::Ref(ty::ReStatic, ..) + ) + }), + hir::ExprKind::Path(ref p) => match cx.tables.qpath_res(p, arg.hir_id) { + hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) => true, + _ => false, + }, + _ => false, + } + } + + fn generate_format_arg_snippet( + cx: &LateContext<'_, '_>, + a: &hir::Expr<'_>, + applicability: &mut Applicability, + ) -> Vec { + if_chain! { + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; + if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; + if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; + + then { + format_arg_expr_tup + .iter() + .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned()) + .collect() + } else { + unreachable!() + } + } + } + + fn is_call(node: &hir::ExprKind<'_>) -> bool { + match node { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { + is_call(&expr.kind) + }, + hir::ExprKind::Call(..) + | hir::ExprKind::MethodCall(..) + // These variants are debatable or require further examination + | hir::ExprKind::Match(..) + | hir::ExprKind::Block{ .. } => true, + _ => false, + } + } + + if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) { + return; + } + + let receiver_type = cx.tables.expr_ty_adjusted(&args[0]); + let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym!(option_type)) { + "||" + } else if is_type_diagnostic_item(cx, receiver_type, sym!(result_type)) { + "|_|" + } else { + return; + }; + + let arg_root = get_arg_root(cx, &args[1]); + + let span_replace_word = method_span.with_hi(expr.span.hi()); + + let mut applicability = Applicability::MachineApplicable; + + //Special handling for `format!` as arg_root + if_chain! { + if let hir::ExprKind::Block(block, None) = &arg_root.kind; + if block.stmts.len() == 1; + if let hir::StmtKind::Local(local) = &block.stmts[0].kind; + if let Some(arg_root) = &local.init; + if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; + if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; + if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; + then { + let fmt_spec = &format_args[0]; + let fmt_args = &format_args[1]; + + let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()]; + + args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability)); + + let sugg = args.join(", "); + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("unwrap_or_else({} panic!({}))", closure_args, sugg), + applicability, + ); + + return; + } + } + + let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); + if requires_to_string(cx, arg_root) { + arg_root_snippet.to_mut().push_str(".to_string()"); + } + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("unwrap_or_else({} {{ panic!({}) }})", closure_args, arg_root_snippet), + applicability, + ); +} + +/// Checks for the `CLONE_ON_COPY` lint. +fn lint_clone_on_copy(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'_>) { + let ty = cx.tables.expr_ty(expr); + if let ty::Ref(_, inner, _) = arg_ty.kind { + if let ty::Ref(_, innermost, _) = inner.kind { + span_lint_and_then( + cx, + CLONE_DOUBLE_REF, + expr.span, + "using `clone` on a double-reference; \ + this will copy the reference instead of cloning the inner type", + |diag| { + if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) { + let mut ty = innermost; + let mut n = 0; + while let ty::Ref(_, inner, _) = ty.kind { + ty = inner; + n += 1; + } + let refs: String = iter::repeat('&').take(n + 1).collect(); + let derefs: String = iter::repeat('*').take(n).collect(); + let explicit = format!("<{}{}>::clone({})", refs, ty, snip); + diag.span_suggestion( + expr.span, + "try dereferencing it", + format!("{}({}{}).clone()", refs, derefs, snip.deref()), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + expr.span, + "or try being explicit if you are sure, that you want to clone a reference", + explicit, + Applicability::MaybeIncorrect, + ); + } + }, + ); + return; // don't report clone_on_copy + } + } + + if is_copy(cx, ty) { + let snip; + if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) { + let parent = cx.tcx.hir().get_parent_node(expr.hir_id); + match &cx.tcx.hir().get(parent) { + hir::Node::Expr(parent) => match parent.kind { + // &*x is a nop, &x.clone() is not + hir::ExprKind::AddrOf(..) => return, + // (*x).func() is useless, x.clone().func() can work in case func borrows mutably + hir::ExprKind::MethodCall(_, _, parent_args) if expr.hir_id == parent_args[0].hir_id => return, + + _ => {}, + }, + hir::Node::Stmt(stmt) => { + if let hir::StmtKind::Local(ref loc) = stmt.kind { + if let hir::PatKind::Ref(..) = loc.pat.kind { + // let ref y = *x borrows x, let ref y = x.clone() does not + return; + } + } + }, + _ => {}, + } + + // x.clone() might have dereferenced x, possibly through Deref impls + if cx.tables.expr_ty(arg) == ty { + snip = Some(("try removing the `clone` call", format!("{}", snippet))); + } else { + let deref_count = cx + .tables + .expr_adjustments(arg) + .iter() + .filter(|adj| { + if let ty::adjustment::Adjust::Deref(_) = adj.kind { + true + } else { + false + } + }) + .count(); + let derefs: String = iter::repeat('*').take(deref_count).collect(); + snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet))); + } + } else { + snip = None; + } + span_lint_and_then(cx, CLONE_ON_COPY, expr.span, "using `clone` on a `Copy` type", |diag| { + if let Some((text, snip)) = snip { + diag.span_suggestion(expr.span, text, snip, Applicability::Unspecified); + } + }); + } +} + +fn lint_clone_on_ref_ptr(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(arg)); + + if let ty::Adt(_, subst) = obj_ty.kind { + let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) { + "Rc" + } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) { + "Arc" + } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) { + "Weak" + } else { + return; + }; + + span_lint_and_sugg( + cx, + CLONE_ON_REF_PTR, + expr.span, + "using `.clone()` on a ref-counted pointer", + "try this", + format!( + "{}::<{}>::clone(&{})", + caller_type, + subst.type_at(0), + snippet(cx, arg.span, "_") + ), + Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak + ); + } +} + +fn lint_string_extend(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let arg = &args[1]; + if let Some(arglists) = method_chain_args(arg, &["chars"]) { + let target = &arglists[0][0]; + let self_ty = walk_ptrs_ty(cx.tables.expr_ty(target)); + let ref_str = if self_ty.kind == ty::Str { + "" + } else if is_type_diagnostic_item(cx, self_ty, sym!(string_type)) { + "&" + } else { + return; + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + STRING_EXTEND_CHARS, + expr.span, + "calling `.extend(_.chars())`", + "try this", + format!( + "{}.push_str({}{})", + snippet_with_applicability(cx, args[0].span, "_", &mut applicability), + ref_str, + snippet_with_applicability(cx, target.span, "_", &mut applicability) + ), + applicability, + ); + } +} + +fn lint_extend(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&args[0])); + if is_type_diagnostic_item(cx, obj_ty, sym!(string_type)) { + lint_string_extend(cx, expr, args); + } +} + +fn lint_cstring_as_ptr(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, source: &hir::Expr<'_>, unwrap: &hir::Expr<'_>) { + if_chain! { + let source_type = cx.tables.expr_ty(source); + if let ty::Adt(def, substs) = source_type.kind; + if cx.tcx.is_diagnostic_item(sym!(result_type), def.did); + if match_type(cx, substs.type_at(0), &paths::CSTRING); + then { + span_lint_and_then( + cx, + TEMPORARY_CSTRING_AS_PTR, + expr.span, + "you are getting the inner pointer of a temporary `CString`", + |diag| { + diag.note("that pointer will be invalid outside this expression"); + diag.span_help(unwrap.span, "assign the `CString` to a variable to extend its lifetime"); + }); + } + } +} + +fn lint_iter_cloned_collect<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &hir::Expr<'_>, + iter_args: &'tcx [hir::Expr<'_>], +) { + if_chain! { + if is_type_diagnostic_item(cx, cx.tables.expr_ty(expr), sym!(vec_type)); + if let Some(slice) = derefs_to_slice(cx, &iter_args[0], cx.tables.expr_ty(&iter_args[0])); + if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite()); + + then { + span_lint_and_sugg( + cx, + ITER_CLONED_COLLECT, + to_replace, + "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \ + more readable", + "try", + ".to_vec()".to_string(), + Applicability::MachineApplicable, + ); + } + } +} + +fn lint_unnecessary_fold(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, fold_args: &[hir::Expr<'_>], fold_span: Span) { + fn check_fold_with_op( + cx: &LateContext<'_, '_>, + expr: &hir::Expr<'_>, + fold_args: &[hir::Expr<'_>], + fold_span: Span, + op: hir::BinOpKind, + replacement_method_name: &str, + replacement_has_args: bool, + ) { + if_chain! { + // Extract the body of the closure passed to fold + if let hir::ExprKind::Closure(_, _, body_id, _, _) = fold_args[2].kind; + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + // Check if the closure body is of the form `acc some_expr(x)` + if let hir::ExprKind::Binary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.kind; + if bin_op.node == op; + + // Extract the names of the two arguments to the closure + if let Some(first_arg_ident) = get_arg_name(&closure_body.params[0].pat); + if let Some(second_arg_ident) = get_arg_name(&closure_body.params[1].pat); + + if match_var(&*left_expr, first_arg_ident); + if replacement_has_args || match_var(&*right_expr, second_arg_ident); + + then { + let mut applicability = Applicability::MachineApplicable; + let sugg = if replacement_has_args { + format!( + "{replacement}(|{s}| {r})", + replacement = replacement_method_name, + s = second_arg_ident, + r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), + ) + } else { + format!( + "{replacement}()", + replacement = replacement_method_name, + ) + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_FOLD, + fold_span.with_hi(expr.span.hi()), + // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f) + "this `.fold` can be written more succinctly using another method", + "try", + sugg, + applicability, + ); + } + } + } + + // Check that this is a call to Iterator::fold rather than just some function called fold + if !match_trait_method(cx, expr, &paths::ITERATOR) { + return; + } + + assert!( + fold_args.len() == 3, + "Expected fold_args to have three entries - the receiver, the initial value and the closure" + ); + + // Check if the first argument to .fold is a suitable literal + if let hir::ExprKind::Lit(ref lit) = fold_args[1].kind { + match lit.node { + ast::LitKind::Bool(false) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Or, "any", true) + }, + ast::LitKind::Bool(true) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::And, "all", true) + }, + ast::LitKind::Int(0, _) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Add, "sum", false) + }, + ast::LitKind::Int(1, _) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Mul, "product", false) + }, + _ => (), + } + } +} + +fn lint_step_by<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, args: &'tcx [hir::Expr<'_>]) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + if let Some((Constant::Int(0), _)) = constant(cx, cx.tables, &args[1]) { + span_lint( + cx, + ITERATOR_STEP_BY_ZERO, + expr.span, + "Iterator::step_by(0) will panic at runtime", + ); + } + } +} + +fn lint_iter_nth<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &hir::Expr<'_>, + nth_and_iter_args: &[&'tcx [hir::Expr<'tcx>]], + is_mut: bool, +) { + let iter_args = nth_and_iter_args[1]; + let mut_str = if is_mut { "_mut" } else { "" }; + let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.tables.expr_ty(&iter_args[0])).is_some() { + "slice" + } else if is_type_diagnostic_item(cx, cx.tables.expr_ty(&iter_args[0]), sym!(vec_type)) { + "Vec" + } else if is_type_diagnostic_item(cx, cx.tables.expr_ty(&iter_args[0]), sym!(vecdeque_type)) { + "VecDeque" + } else { + let nth_args = nth_and_iter_args[0]; + lint_iter_nth_zero(cx, expr, &nth_args); + return; // caller is not a type that we want to lint + }; + + span_lint_and_help( + cx, + ITER_NTH, + expr.span, + &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type), + None, + &format!("calling `.get{}()` is both faster and more readable", mut_str), + ); +} + +fn lint_iter_nth_zero<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, nth_args: &'tcx [hir::Expr<'_>]) { + if_chain! { + if match_trait_method(cx, expr, &paths::ITERATOR); + if let Some((Constant::Int(0), _)) = constant(cx, cx.tables, &nth_args[1]); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NTH_ZERO, + expr.span, + "called `.nth(0)` on a `std::iter::Iterator`", + "try calling", + format!("{}.next()", snippet_with_applicability(cx, nth_args[0].span, "..", &mut applicability)), + applicability, + ); + } + } +} + +fn lint_get_unwrap<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &hir::Expr<'_>, + get_args: &'tcx [hir::Expr<'_>], + is_mut: bool, +) { + // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`, + // because they do not implement `IndexMut` + let mut applicability = Applicability::MachineApplicable; + let expr_ty = cx.tables.expr_ty(&get_args[0]); + let get_args_str = if get_args.len() > 1 { + snippet_with_applicability(cx, get_args[1].span, "_", &mut applicability) + } else { + return; // not linting on a .get().unwrap() chain or variant + }; + let mut needs_ref; + let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() { + needs_ref = get_args_str.parse::().is_ok(); + "slice" + } else if is_type_diagnostic_item(cx, expr_ty, sym!(vec_type)) { + needs_ref = get_args_str.parse::().is_ok(); + "Vec" + } else if is_type_diagnostic_item(cx, expr_ty, sym!(vecdeque_type)) { + needs_ref = get_args_str.parse::().is_ok(); + "VecDeque" + } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym!(hashmap_type)) { + needs_ref = true; + "HashMap" + } else if !is_mut && match_type(cx, expr_ty, &paths::BTREEMAP) { + needs_ref = true; + "BTreeMap" + } else { + return; // caller is not a type that we want to lint + }; + + let mut span = expr.span; + + // Handle the case where the result is immediately dereferenced + // by not requiring ref and pulling the dereference into the + // suggestion. + if_chain! { + if needs_ref; + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(hir::UnOp::UnDeref, _) = parent.kind; + then { + needs_ref = false; + span = parent.span; + } + } + + let mut_str = if is_mut { "_mut" } else { "" }; + let borrow_str = if !needs_ref { + "" + } else if is_mut { + "&mut " + } else { + "&" + }; + + span_lint_and_sugg( + cx, + GET_UNWRAP, + span, + &format!( + "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise", + mut_str, caller_type + ), + "try this", + format!( + "{}{}[{}]", + borrow_str, + snippet_with_applicability(cx, get_args[0].span, "_", &mut applicability), + get_args_str + ), + applicability, + ); +} + +fn lint_iter_skip_next(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) { + // lint if caller of skip is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + span_lint_and_help( + cx, + ITER_SKIP_NEXT, + expr.span, + "called `skip(x).next()` on an iterator", + None, + "this is more succinctly expressed by calling `nth(x)`", + ); + } +} + +fn derefs_to_slice<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ty: Ty<'tcx>, +) -> Option<&'tcx hir::Expr<'tcx>> { + fn may_slice<'a>(cx: &LateContext<'_, 'a>, ty: Ty<'a>) -> bool { + match ty.kind { + ty::Slice(_) => true, + ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()), + ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym!(vec_type)), + ty::Array(_, size) => { + if let Some(size) = size.try_eval_usize(cx.tcx, cx.param_env) { + size < 32 + } else { + false + } + }, + ty::Ref(_, inner, _) => may_slice(cx, inner), + _ => false, + } + } + + if let hir::ExprKind::MethodCall(ref path, _, ref args) = expr.kind { + if path.ident.name == sym!(iter) && may_slice(cx, cx.tables.expr_ty(&args[0])) { + Some(&args[0]) + } else { + None + } + } else { + match ty.kind { + ty::Slice(_) => Some(expr), + ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr), + ty::Ref(_, inner, _) => { + if may_slice(cx, inner) { + Some(expr) + } else { + None + } + }, + _ => None, + } + } +} + +/// lint use of `unwrap()` for `Option`s and `Result`s +fn lint_unwrap(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, unwrap_args: &[hir::Expr<'_>]) { + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&unwrap_args[0])); + + let mess = if is_type_diagnostic_item(cx, obj_ty, sym!(option_type)) { + Some((OPTION_UNWRAP_USED, "an Option", "None")) + } else if is_type_diagnostic_item(cx, obj_ty, sym!(result_type)) { + Some((RESULT_UNWRAP_USED, "a Result", "Err")) + } else { + None + }; + + if let Some((lint, kind, none_value)) = mess { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("used `unwrap()` on `{}` value", kind,), + None, + &format!( + "if you don't want to handle the `{}` case gracefully, consider \ + using `expect()` to provide a better panic message", + none_value, + ), + ); + } +} + +/// lint use of `expect()` for `Option`s and `Result`s +fn lint_expect(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, expect_args: &[hir::Expr<'_>]) { + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&expect_args[0])); + + let mess = if is_type_diagnostic_item(cx, obj_ty, sym!(option_type)) { + Some((OPTION_EXPECT_USED, "an Option", "None")) + } else if is_type_diagnostic_item(cx, obj_ty, sym!(result_type)) { + Some((RESULT_EXPECT_USED, "a Result", "Err")) + } else { + None + }; + + if let Some((lint, kind, none_value)) = mess { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("used `expect()` on `{}` value", kind,), + None, + &format!("if this value is an `{}`, it will panic", none_value,), + ); + } +} + +/// lint use of `ok().expect()` for `Result`s +fn lint_ok_expect(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, ok_args: &[hir::Expr<'_>]) { + if_chain! { + // lint if the caller of `ok()` is a `Result` + if is_type_diagnostic_item(cx, cx.tables.expr_ty(&ok_args[0]), sym!(result_type)); + let result_type = cx.tables.expr_ty(&ok_args[0]); + if let Some(error_type) = get_error_type(cx, result_type); + if has_debug_impl(error_type, cx); + + then { + span_lint_and_help( + cx, + OK_EXPECT, + expr.span, + "called `ok().expect()` on a `Result` value", + None, + "you can call `expect()` directly on the `Result`", + ); + } + } +} + +/// lint use of `map().flatten()` for `Iterators` and 'Options' +fn lint_map_flatten<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.map().flatten()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `map(..).flatten()` on an `Iterator`. \ + This is more succinctly expressed by calling `.flat_map(..)`"; + let self_snippet = snippet(cx, map_args[0].span, ".."); + let func_snippet = snippet(cx, map_args[1].span, ".."); + let hint = format!("{0}.flat_map({1})", self_snippet, func_snippet); + span_lint_and_sugg( + cx, + MAP_FLATTEN, + expr.span, + msg, + "try using `flat_map` instead", + hint, + Applicability::MachineApplicable, + ); + } + + // lint if caller of `.map().flatten()` is an Option + if is_type_diagnostic_item(cx, cx.tables.expr_ty(&map_args[0]), sym!(option_type)) { + let msg = "called `map(..).flatten()` on an `Option`. \ + This is more succinctly expressed by calling `.and_then(..)`"; + let self_snippet = snippet(cx, map_args[0].span, ".."); + let func_snippet = snippet(cx, map_args[1].span, ".."); + let hint = format!("{0}.and_then({1})", self_snippet, func_snippet); + span_lint_and_sugg( + cx, + MAP_FLATTEN, + expr.span, + msg, + "try using `and_then` instead", + hint, + Applicability::MachineApplicable, + ); + } +} + +/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s +fn lint_map_unwrap_or_else<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + map_args: &'tcx [hir::Expr<'_>], + unwrap_args: &'tcx [hir::Expr<'_>], +) { + // lint if the caller of `map()` is an `Option` + let is_option = is_type_diagnostic_item(cx, cx.tables.expr_ty(&map_args[0]), sym!(option_type)); + let is_result = is_type_diagnostic_item(cx, cx.tables.expr_ty(&map_args[0]), sym!(result_type)); + + if is_option || is_result { + // Don't make a suggestion that may fail to compile due to mutably borrowing + // the same variable twice. + let map_mutated_vars = mutated_variables(&map_args[0], cx); + let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx); + if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { + if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { + return; + } + } else { + return; + } + + // lint message + let msg = if is_option { + "called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling \ + `map_or_else(g, f)` instead" + } else { + "called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling \ + `.map_or_else(g, f)` instead" + }; + // get snippets for args to map() and unwrap_or_else() + let map_snippet = snippet(cx, map_args[1].span, ".."); + let unwrap_snippet = snippet(cx, unwrap_args[1].span, ".."); + // lint, with note if neither arg is > 1 line and both map() and + // unwrap_or_else() have the same span + let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1; + let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt(); + if same_span && !multiline { + span_lint_and_note( + cx, + if is_option { + OPTION_MAP_UNWRAP_OR_ELSE + } else { + RESULT_MAP_UNWRAP_OR_ELSE + }, + expr.span, + msg, + None, + &format!( + "replace `map({0}).unwrap_or_else({1})` with `map_or_else({1}, {0})`", + map_snippet, unwrap_snippet, + ), + ); + } else if same_span && multiline { + span_lint( + cx, + if is_option { + OPTION_MAP_UNWRAP_OR_ELSE + } else { + RESULT_MAP_UNWRAP_OR_ELSE + }, + expr.span, + msg, + ); + }; + } +} + +/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s +fn lint_map_or_none<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + map_or_args: &'tcx [hir::Expr<'_>], +) { + let is_option = is_type_diagnostic_item(cx, cx.tables.expr_ty(&map_or_args[0]), sym!(option_type)); + let is_result = is_type_diagnostic_item(cx, cx.tables.expr_ty(&map_or_args[0]), sym!(result_type)); + + // There are two variants of this `map_or` lint: + // (1) using `map_or` as an adapter from `Result` to `Option` + // (2) using `map_or` as a combinator instead of `and_then` + // + // (For this lint) we don't care if any other type calls `map_or` + if !is_option && !is_result { + return; + } + + let (lint_name, msg, instead, hint) = { + let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind { + match_qpath(qpath, &paths::OPTION_NONE) + } else { + return; + }; + + if !default_arg_is_none { + // nothing to lint! + return; + } + + let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind { + match_qpath(qpath, &paths::OPTION_SOME) + } else { + false + }; + + if is_option { + let self_snippet = snippet(cx, map_or_args[0].span, ".."); + let func_snippet = snippet(cx, map_or_args[2].span, ".."); + let msg = "called `map_or(None, f)` on an `Option` value. This can be done more directly by calling \ + `and_then(f)` instead"; + ( + OPTION_MAP_OR_NONE, + msg, + "try using `and_then` instead", + format!("{0}.and_then({1})", self_snippet, func_snippet), + ) + } else if f_arg_is_some { + let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ + `ok()` instead"; + let self_snippet = snippet(cx, map_or_args[0].span, ".."); + ( + RESULT_MAP_OR_INTO_OPTION, + msg, + "try using `ok` instead", + format!("{0}.ok()", self_snippet), + ) + } else { + // nothing to lint! + return; + } + }; + + span_lint_and_sugg( + cx, + lint_name, + expr.span, + msg, + instead, + hint, + Applicability::MachineApplicable, + ); +} + +/// Lint use of `_.and_then(|x| Some(y))` for `Option`s +fn lint_option_and_then_some(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + const LINT_MSG: &str = "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"; + const NO_OP_MSG: &str = "using `Option.and_then(Some)`, which is a no-op"; + + let ty = cx.tables.expr_ty(&args[0]); + if !is_type_diagnostic_item(cx, ty, sym!(option_type)) { + return; + } + + match args[1].kind { + hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + if_chain! { + if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind; + if let hir::ExprKind::Path(ref qpath) = some_expr.kind; + if match_qpath(qpath, &paths::OPTION_SOME); + if some_args.len() == 1; + then { + let inner_expr = &some_args[0]; + + if contains_return(inner_expr) { + return; + } + + let some_inner_snip = if inner_expr.span.from_expansion() { + snippet_with_macro_callsite(cx, inner_expr.span, "_") + } else { + snippet(cx, inner_expr.span, "_") + }; + + let closure_args_snip = snippet(cx, closure_args_span, ".."); + let option_snip = snippet(cx, args[0].span, ".."); + let note = format!("{}.map({} {})", option_snip, closure_args_snip, some_inner_snip); + span_lint_and_sugg( + cx, + OPTION_AND_THEN_SOME, + expr.span, + LINT_MSG, + "try this", + note, + Applicability::MachineApplicable, + ); + } + } + }, + // `_.and_then(Some)` case, which is no-op. + hir::ExprKind::Path(ref qpath) => { + if match_qpath(qpath, &paths::OPTION_SOME) { + let option_snip = snippet(cx, args[0].span, ".."); + let note = format!("{}", option_snip); + span_lint_and_sugg( + cx, + OPTION_AND_THEN_SOME, + expr.span, + NO_OP_MSG, + "use the expression directly", + note, + Applicability::MachineApplicable, + ); + } + }, + _ => {}, + } +} + +/// lint use of `filter().next()` for `Iterators` +fn lint_filter_next<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + filter_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().next()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling \ + `.find(p)` instead."; + let filter_snippet = snippet(cx, filter_args[1].span, ".."); + if filter_snippet.lines().count() <= 1 { + // add note if not multi-line + span_lint_and_note( + cx, + FILTER_NEXT, + expr.span, + msg, + None, + &format!("replace `filter({0}).next()` with `find({0})`", filter_snippet), + ); + } else { + span_lint(cx, FILTER_NEXT, expr.span, msg); + } + } +} + +/// lint use of `skip_while().next()` for `Iterators` +fn lint_skip_while_next<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + _skip_while_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.skip_while().next()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + span_lint_and_help( + cx, + SKIP_WHILE_NEXT, + expr.span, + "called `skip_while(p).next()` on an `Iterator`", + None, + "this is more succinctly expressed by calling `.find(!p)` instead", + ); + } +} + +/// lint use of `filter().map()` for `Iterators` +fn lint_filter_map<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter(p).map(q)` on an `Iterator`"; + let hint = "this is more succinctly expressed by calling `.filter_map(..)` instead"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} + +/// lint use of `filter_map().next()` for `Iterators` +fn lint_filter_map_next<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + filter_args: &'tcx [hir::Expr<'_>], +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling \ + `.find_map(p)` instead."; + let filter_snippet = snippet(cx, filter_args[1].span, ".."); + if filter_snippet.lines().count() <= 1 { + span_lint_and_note( + cx, + FILTER_MAP_NEXT, + expr.span, + msg, + None, + &format!("replace `filter_map({0}).next()` with `find_map({0})`", filter_snippet), + ); + } else { + span_lint(cx, FILTER_MAP_NEXT, expr.span, msg); + } + } +} + +/// lint use of `find().map()` for `Iterators` +fn lint_find_map<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + _find_args: &'tcx [hir::Expr<'_>], + map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().map()` is an Iterator + if match_trait_method(cx, &map_args[0], &paths::ITERATOR) { + let msg = "called `find(p).map(q)` on an `Iterator`"; + let hint = "this is more succinctly expressed by calling `.find_map(..)` instead"; + span_lint_and_help(cx, FIND_MAP, expr.span, msg, None, hint); + } +} + +/// lint use of `filter_map().map()` for `Iterators` +fn lint_filter_map_map<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter_map(p).map(q)` on an `Iterator`"; + let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} + +/// lint use of `filter().flat_map()` for `Iterators` +fn lint_filter_flat_map<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().flat_map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter(p).flat_map(q)` on an `Iterator`"; + let hint = "this is more succinctly expressed by calling `.flat_map(..)` \ + and filtering by returning `iter::empty()`"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} + +/// lint use of `filter_map().flat_map()` for `Iterators` +fn lint_filter_map_flat_map<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter_map().flat_map()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter_map(p).flat_map(q)` on an `Iterator`"; + let hint = "this is more succinctly expressed by calling `.flat_map(..)` \ + and filtering by returning `iter::empty()`"; + span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); + } +} + +/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient +fn lint_flat_map_identity<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + flat_map_args: &'tcx [hir::Expr<'_>], + flat_map_span: Span, +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let arg_node = &flat_map_args[1].kind; + + let apply_lint = |message: &str| { + span_lint_and_sugg( + cx, + FLAT_MAP_IDENTITY, + flat_map_span.with_hi(expr.span.hi()), + message, + "try", + "flatten()".to_string(), + Applicability::MachineApplicable, + ); + }; + + if_chain! { + if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node; + let body = cx.tcx.hir().body(*body_id); + + if let hir::PatKind::Binding(_, _, binding_ident, _) = body.params[0].pat.kind; + if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = body.value.kind; + + if path.segments.len() == 1; + if path.segments[0].ident.as_str() == binding_ident.as_str(); + + then { + apply_lint("called `flat_map(|x| x)` on an `Iterator`"); + } + } + + if_chain! { + if let hir::ExprKind::Path(ref qpath) = arg_node; + + if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY); + + then { + apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`"); + } + } + } +} + +/// lint searching an Iterator followed by `is_some()` +fn lint_search_is_some<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx hir::Expr<'_>, + search_method: &str, + search_args: &'tcx [hir::Expr<'_>], + is_some_args: &'tcx [hir::Expr<'_>], + method_span: Span, +) { + // lint if caller of search is an Iterator + if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) { + let msg = format!( + "called `is_some()` after searching an `Iterator` with {}. This is more succinctly \ + expressed by calling `any()`.", + search_method + ); + let search_snippet = snippet(cx, search_args[1].span, ".."); + if search_snippet.lines().count() <= 1 { + // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` + // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` + let any_search_snippet = if_chain! { + if search_method == "find"; + if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind; + let closure_body = cx.tcx.hir().body(body_id); + if let Some(closure_arg) = closure_body.params.get(0); + then { + if let hir::PatKind::Ref(..) = closure_arg.pat.kind { + Some(search_snippet.replacen('&', "", 1)) + } else if let Some(name) = get_arg_name(&closure_arg.pat) { + Some(search_snippet.replace(&format!("*{}", name), &name.as_str())) + } else { + None + } + } else { + None + } + }; + // add note if not multi-line + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + &msg, + "try this", + format!( + "any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, SEARCH_IS_SOME, expr.span, &msg); + } + } +} + +/// Used for `lint_binary_expr_with_method_call`. +#[derive(Copy, Clone)] +struct BinaryExprInfo<'a> { + expr: &'a hir::Expr<'a>, + chain: &'a hir::Expr<'a>, + other: &'a hir::Expr<'a>, + eq: bool, +} + +/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. +fn lint_binary_expr_with_method_call(cx: &LateContext<'_, '_>, info: &mut BinaryExprInfo<'_>) { + macro_rules! lint_with_both_lhs_and_rhs { + ($func:ident, $cx:expr, $info:ident) => { + if !$func($cx, $info) { + ::std::mem::swap(&mut $info.chain, &mut $info.other); + if $func($cx, $info) { + return; + } + } + }; + } + + lint_with_both_lhs_and_rhs!(lint_chars_next_cmp, cx, info); + lint_with_both_lhs_and_rhs!(lint_chars_last_cmp, cx, info); + lint_with_both_lhs_and_rhs!(lint_chars_next_cmp_with_unwrap, cx, info); + lint_with_both_lhs_and_rhs!(lint_chars_last_cmp_with_unwrap, cx, info); +} + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. +fn lint_chars_cmp( + cx: &LateContext<'_, '_>, + info: &BinaryExprInfo<'_>, + chain_methods: &[&str], + lint: &'static Lint, + suggest: &str, +) -> bool { + if_chain! { + if let Some(args) = method_chain_args(info.chain, chain_methods); + if let hir::ExprKind::Call(ref fun, ref arg_char) = info.other.kind; + if arg_char.len() == 1; + if let hir::ExprKind::Path(ref qpath) = fun.kind; + if let Some(segment) = single_segment_path(qpath); + if segment.ident.name == sym!(Some); + then { + let mut applicability = Applicability::MachineApplicable; + let self_ty = walk_ptrs_ty(cx.tables.expr_ty_adjusted(&args[0][0])); + + if self_ty.kind != ty::Str { + return false; + } + + span_lint_and_sugg( + cx, + lint, + info.expr.span, + &format!("you should use the `{}` method", suggest), + "like this", + format!("{}{}.{}({})", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0][0].span, "_", &mut applicability), + suggest, + snippet_with_applicability(cx, arg_char[0].span, "_", &mut applicability)), + applicability, + ); + + return true; + } + } + + false +} + +/// Checks for the `CHARS_NEXT_CMP` lint. +fn lint_chars_next_cmp<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, info: &BinaryExprInfo<'_>) -> bool { + lint_chars_cmp(cx, info, &["chars", "next"], CHARS_NEXT_CMP, "starts_with") +} + +/// Checks for the `CHARS_LAST_CMP` lint. +fn lint_chars_last_cmp<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, info: &BinaryExprInfo<'_>) -> bool { + if lint_chars_cmp(cx, info, &["chars", "last"], CHARS_LAST_CMP, "ends_with") { + true + } else { + lint_chars_cmp(cx, info, &["chars", "next_back"], CHARS_LAST_CMP, "ends_with") + } +} + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. +fn lint_chars_cmp_with_unwrap<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + info: &BinaryExprInfo<'_>, + chain_methods: &[&str], + lint: &'static Lint, + suggest: &str, +) -> bool { + if_chain! { + if let Some(args) = method_chain_args(info.chain, chain_methods); + if let hir::ExprKind::Lit(ref lit) = info.other.kind; + if let ast::LitKind::Char(c) = lit.node; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + lint, + info.expr.span, + &format!("you should use the `{}` method", suggest), + "like this", + format!("{}{}.{}('{}')", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0][0].span, "_", &mut applicability), + suggest, + c), + applicability, + ); + + true + } else { + false + } + } +} + +/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. +fn lint_chars_next_cmp_with_unwrap<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, info: &BinaryExprInfo<'_>) -> bool { + lint_chars_cmp_with_unwrap(cx, info, &["chars", "next", "unwrap"], CHARS_NEXT_CMP, "starts_with") +} + +/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. +fn lint_chars_last_cmp_with_unwrap<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, info: &BinaryExprInfo<'_>) -> bool { + if lint_chars_cmp_with_unwrap(cx, info, &["chars", "last", "unwrap"], CHARS_LAST_CMP, "ends_with") { + true + } else { + lint_chars_cmp_with_unwrap(cx, info, &["chars", "next_back", "unwrap"], CHARS_LAST_CMP, "ends_with") + } +} + +/// lint for length-1 `str`s for methods in `PATTERN_METHODS` +fn lint_single_char_pattern<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + _expr: &'tcx hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, +) { + if_chain! { + if let hir::ExprKind::Lit(lit) = &arg.kind; + if let ast::LitKind::Str(r, style) = lit.node; + if r.as_str().len() == 1; + then { + let mut applicability = Applicability::MachineApplicable; + let snip = snippet_with_applicability(cx, arg.span, "..", &mut applicability); + let ch = if let ast::StrStyle::Raw(nhash) = style { + let nhash = nhash as usize; + // for raw string: r##"a"## + &snip[(nhash + 2)..(snip.len() - 1 - nhash)] + } else { + // for regular string: "a" + &snip[1..(snip.len() - 1)] + }; + let hint = format!("'{}'", if ch == "'" { "\\'" } else { ch }); + span_lint_and_sugg( + cx, + SINGLE_CHAR_PATTERN, + arg.span, + "single-character string constant used as pattern", + "try using a `char` instead", + hint, + applicability, + ); + } + } +} + +/// Checks for the `USELESS_ASREF` lint. +fn lint_asref(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) { + // when we get here, we've already checked that the call name is "as_ref" or "as_mut" + // check if the call is to the actual `AsRef` or `AsMut` trait + if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) { + // check if the type after `as_ref` or `as_mut` is the same as before + let recvr = &as_ref_args[0]; + let rcv_ty = cx.tables.expr_ty(recvr); + let res_ty = cx.tables.expr_ty(expr); + let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); + let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); + if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { + // allow the `as_ref` or `as_mut` if it is followed by another method call + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::MethodCall(_, ref span, _) = parent.kind; + if span != &expr.span; + then { + return; + } + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + USELESS_ASREF, + expr.span, + &format!("this call to `{}` does nothing", call_name), + "try this", + snippet_with_applicability(cx, recvr.span, "_", &mut applicability).to_string(), + applicability, + ); + } + } +} + +fn ty_has_iter_method(cx: &LateContext<'_, '_>, self_ref_ty: Ty<'_>) -> Option<(&'static str, &'static str)> { + has_iter_method(cx, self_ref_ty).map(|ty_name| { + let mutbl = match self_ref_ty.kind { + ty::Ref(_, _, mutbl) => mutbl, + _ => unreachable!(), + }; + let method_name = match mutbl { + hir::Mutability::Not => "iter", + hir::Mutability::Mut => "iter_mut", + }; + (ty_name, method_name) + }) +} + +fn lint_into_iter(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, self_ref_ty: Ty<'_>, method_span: Span) { + if !match_trait_method(cx, expr, &paths::INTO_ITERATOR) { + return; + } + if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ref_ty) { + span_lint_and_sugg( + cx, + INTO_ITER_ON_REF, + method_span, + &format!( + "this `.into_iter()` call is equivalent to `.{}()` and will not move the `{}`", + method_name, kind, + ), + "call directly", + method_name.to_string(), + Applicability::MachineApplicable, + ); + } +} + +/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) +fn lint_maybe_uninit(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, outer: &hir::Expr<'_>) { + if_chain! { + if let hir::ExprKind::Call(ref callee, ref args) = expr.kind; + if args.is_empty(); + if let hir::ExprKind::Path(ref path) = callee.kind; + if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT); + if !is_maybe_uninit_ty_valid(cx, cx.tables.expr_ty_adjusted(outer)); + then { + span_lint( + cx, + UNINIT_ASSUMED_INIT, + outer.span, + "this call for this type may be undefined behavior" + ); + } + } +} + +fn is_maybe_uninit_ty_valid(cx: &LateContext<'_, '_>, ty: Ty<'_>) -> bool { + match ty.kind { + ty::Array(ref component, _) => is_maybe_uninit_ty_valid(cx, component), + ty::Tuple(ref types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)), + ty::Adt(ref adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), + _ => false, + } +} + +fn lint_suspicious_map(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) { + span_lint_and_help( + cx, + SUSPICIOUS_MAP, + expr.span, + "this call to `map()` won't have an effect on the call to `count()`", + None, + "make sure you did not confuse `map` with `filter` or `for_each`", + ); +} + +/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s +fn lint_option_as_ref_deref<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &hir::Expr<'_>, + as_ref_args: &[hir::Expr<'_>], + map_args: &[hir::Expr<'_>], + is_mut: bool, +) { + let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not); + + let option_ty = cx.tables.expr_ty(&as_ref_args[0]); + if !is_type_diagnostic_item(cx, option_ty, sym!(option_type)) { + return; + } + + let deref_aliases: [&[&str]; 9] = [ + &paths::DEREF_TRAIT_METHOD, + &paths::DEREF_MUT_TRAIT_METHOD, + &paths::CSTRING_AS_C_STR, + &paths::OS_STRING_AS_OS_STR, + &paths::PATH_BUF_AS_PATH, + &paths::STRING_AS_STR, + &paths::STRING_AS_MUT_STR, + &paths::VEC_AS_SLICE, + &paths::VEC_AS_MUT_SLICE, + ]; + + let is_deref = match map_args[1].kind { + hir::ExprKind::Path(ref expr_qpath) => deref_aliases.iter().any(|path| match_qpath(expr_qpath, path)), + hir::ExprKind::Closure(_, _, body_id, _, _) => { + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + match &closure_expr.kind { + hir::ExprKind::MethodCall(_, _, args) => { + if_chain! { + if args.len() == 1; + if let hir::ExprKind::Path(qpath) = &args[0].kind; + if let hir::def::Res::Local(local_id) = cx.tables.qpath_res(qpath, args[0].hir_id); + if closure_body.params[0].pat.hir_id == local_id; + let adj = cx.tables.expr_adjustments(&args[0]).iter().map(|x| &x.kind).collect::>(); + if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj; + then { + let method_did = cx.tables.type_dependent_def_id(closure_expr.hir_id).unwrap(); + deref_aliases.iter().any(|path| match_def_path(cx, method_did, path)) + } else { + false + } + } + }, + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref inner) if same_mutability(m) => { + if_chain! { + if let hir::ExprKind::Unary(hir::UnOp::UnDeref, ref inner1) = inner.kind; + if let hir::ExprKind::Unary(hir::UnOp::UnDeref, ref inner2) = inner1.kind; + if let hir::ExprKind::Path(ref qpath) = inner2.kind; + if let hir::def::Res::Local(local_id) = cx.tables.qpath_res(qpath, inner2.hir_id); + then { + closure_body.params[0].pat.hir_id == local_id + } else { + false + } + } + }, + _ => false, + } + }, + _ => false, + }; + + if is_deref { + let current_method = if is_mut { + format!(".as_mut().map({})", snippet(cx, map_args[1].span, "..")) + } else { + format!(".as_ref().map({})", snippet(cx, map_args[1].span, "..")) + }; + let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" }; + let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint); + let suggestion = format!("try using {} instead", method_hint); + + let msg = format!( + "called `{0}` on an Option value. This can be done more directly \ + by calling `{1}` instead", + current_method, hint + ); + span_lint_and_sugg( + cx, + OPTION_AS_REF_DEREF, + expr.span, + &msg, + &suggestion, + hint, + Applicability::MachineApplicable, + ); + } +} + +/// Given a `Result` type, return its error type (`E`). +fn get_error_type<'a>(cx: &LateContext<'_, '_>, ty: Ty<'a>) -> Option> { + match ty.kind { + ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym!(result_type)) => substs.types().nth(1), + _ => None, + } +} + +/// This checks whether a given type is known to implement Debug. +fn has_debug_impl<'a, 'b>(ty: Ty<'a>, cx: &LateContext<'b, 'a>) -> bool { + cx.tcx + .get_diagnostic_item(sym::debug_trait) + .map_or(false, |debug| implements_trait(cx, ty, debug, &[])) +} + +enum Convention { + Eq(&'static str), + StartsWith(&'static str), +} + +#[rustfmt::skip] +const CONVENTIONS: [(Convention, &[SelfKind]); 7] = [ + (Convention::Eq("new"), &[SelfKind::No]), + (Convention::StartsWith("as_"), &[SelfKind::Ref, SelfKind::RefMut]), + (Convention::StartsWith("from_"), &[SelfKind::No]), + (Convention::StartsWith("into_"), &[SelfKind::Value]), + (Convention::StartsWith("is_"), &[SelfKind::Ref, SelfKind::No]), + (Convention::Eq("to_mut"), &[SelfKind::RefMut]), + (Convention::StartsWith("to_"), &[SelfKind::Ref]), +]; + +const FN_HEADER: hir::FnHeader = hir::FnHeader { + unsafety: hir::Unsafety::Normal, + constness: hir::Constness::NotConst, + asyncness: hir::IsAsync::NotAsync, + abi: rustc_target::spec::abi::Abi::Rust, +}; + +#[rustfmt::skip] +const TRAIT_METHODS: [(&str, usize, &hir::FnHeader, SelfKind, OutType, &str); 30] = [ + ("add", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Add"), + ("as_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::convert::AsMut"), + ("as_ref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::convert::AsRef"), + ("bitand", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitAnd"), + ("bitor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitOr"), + ("bitxor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitXor"), + ("borrow", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::borrow::Borrow"), + ("borrow_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::borrow::BorrowMut"), + ("clone", 1, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::clone::Clone"), + ("cmp", 2, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::cmp::Ord"), + ("default", 0, &FN_HEADER, SelfKind::No, OutType::Any, "std::default::Default"), + ("deref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Deref"), + ("deref_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::DerefMut"), + ("div", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Div"), + ("drop", 1, &FN_HEADER, SelfKind::RefMut, OutType::Unit, "std::ops::Drop"), + ("eq", 2, &FN_HEADER, SelfKind::Ref, OutType::Bool, "std::cmp::PartialEq"), + ("from_iter", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::iter::FromIterator"), + ("from_str", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::str::FromStr"), + ("hash", 2, &FN_HEADER, SelfKind::Ref, OutType::Unit, "std::hash::Hash"), + ("index", 2, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Index"), + ("index_mut", 2, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::IndexMut"), + ("into_iter", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::iter::IntoIterator"), + ("mul", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Mul"), + ("neg", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Neg"), + ("next", 1, &FN_HEADER, SelfKind::RefMut, OutType::Any, "std::iter::Iterator"), + ("not", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Not"), + ("rem", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Rem"), + ("shl", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shl"), + ("shr", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shr"), + ("sub", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Sub"), +]; + +#[rustfmt::skip] +const PATTERN_METHODS: [(&str, usize); 17] = [ + ("contains", 1), + ("starts_with", 1), + ("ends_with", 1), + ("find", 1), + ("rfind", 1), + ("split", 1), + ("rsplit", 1), + ("split_terminator", 1), + ("rsplit_terminator", 1), + ("splitn", 2), + ("rsplitn", 2), + ("matches", 1), + ("rmatches", 1), + ("match_indices", 1), + ("rmatch_indices", 1), + ("trim_start_matches", 1), + ("trim_end_matches", 1), +]; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum SelfKind { + Value, + Ref, + RefMut, + No, +} + +impl SelfKind { + fn matches<'a>(self, cx: &LateContext<'_, 'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + fn matches_value<'a>(cx: &LateContext<'_, 'a>, parent_ty: Ty<'_>, ty: Ty<'_>) -> bool { + if ty == parent_ty { + true + } else if ty.is_box() { + ty.boxed_ty() == parent_ty + } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) { + if let ty::Adt(_, substs) = ty.kind { + substs.types().next().map_or(false, |t| t == parent_ty) + } else { + false + } + } else { + false + } + } + + fn matches_ref<'a>( + cx: &LateContext<'_, 'a>, + mutability: hir::Mutability, + parent_ty: Ty<'a>, + ty: Ty<'a>, + ) -> bool { + if let ty::Ref(_, t, m) = ty.kind { + return m == mutability && t == parent_ty; + } + + let trait_path = match mutability { + hir::Mutability::Not => &paths::ASREF_TRAIT, + hir::Mutability::Mut => &paths::ASMUT_TRAIT, + }; + + let trait_def_id = match get_trait_def_id(cx, trait_path) { + Some(did) => did, + None => return false, + }; + implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) + } + + match self { + Self::Value => matches_value(cx, parent_ty, ty), + Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), + Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty), + Self::No => ty != parent_ty, + } + } + + #[must_use] + fn description(self) -> &'static str { + match self { + Self::Value => "self by value", + Self::Ref => "self by reference", + Self::RefMut => "self by mutable reference", + Self::No => "no self", + } + } +} + +impl Convention { + #[must_use] + fn check(&self, other: &str) -> bool { + match *self { + Self::Eq(this) => this == other, + Self::StartsWith(this) => other.starts_with(this) && this != other, + } + } +} + +impl fmt::Display for Convention { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + Self::Eq(this) => this.fmt(f), + Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)), + } + } +} + +#[derive(Clone, Copy)] +enum OutType { + Unit, + Bool, + Any, + Ref, +} + +impl OutType { + fn matches(self, cx: &LateContext<'_, '_>, ty: &hir::FnRetTy<'_>) -> bool { + let is_unit = |ty: &hir::Ty<'_>| SpanlessEq::new(cx).eq_ty_kind(&ty.kind, &hir::TyKind::Tup(&[])); + match (self, ty) { + (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, + (Self::Unit, &hir::FnRetTy::Return(ref ty)) if is_unit(ty) => true, + (Self::Bool, &hir::FnRetTy::Return(ref ty)) if is_bool(ty) => true, + (Self::Any, &hir::FnRetTy::Return(ref ty)) if !is_unit(ty) => true, + (Self::Ref, &hir::FnRetTy::Return(ref ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)), + _ => false, + } + } +} + +fn is_bool(ty: &hir::Ty<'_>) -> bool { + if let hir::TyKind::Path(ref p) = ty.kind { + match_qpath(p, &["bool"]) + } else { + false + } +} + +// Returns `true` if `expr` contains a return expression +fn contains_return(expr: &hir::Expr<'_>) -> bool { + struct RetCallFinder { + found: bool, + } + + impl<'tcx> intravisit::Visitor<'tcx> for RetCallFinder { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if self.found { + return; + } + if let hir::ExprKind::Ret(..) = &expr.kind { + self.found = true; + } else { + intravisit::walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } + } + + let mut visitor = RetCallFinder { found: false }; + visitor.visit_expr(expr); + visitor.found +} + +fn check_pointer_offset(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if_chain! { + if args.len() == 2; + if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.tables.expr_ty(&args[0]).kind; + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + if layout.is_zst(); + then { + span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); + } + } +} + +fn lint_filetype_is_file(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let ty = cx.tables.expr_ty(&args[0]); + + if !match_type(cx, ty, &paths::FILE_TYPE) { + return; + } + + let span: Span; + let verb: &str; + let lint_unary: &str; + let help_unary: &str; + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(op, _) = parent.kind; + if op == hir::UnOp::UnNot; + then { + lint_unary = "!"; + verb = "denies"; + help_unary = ""; + span = parent.span; + } else { + lint_unary = ""; + verb = "covers"; + help_unary = "!"; + span = expr.span; + } + } + let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb); + let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary); + span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg); +} + +fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { + expected.constness == actual.constness + && expected.unsafety == actual.unsafety + && expected.asyncness == actual.asyncness +} diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs new file mode 100644 index 0000000000000..bf9dd3c936929 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -0,0 +1,135 @@ +use crate::utils::{differing_macro_contexts, snippet_with_applicability, span_lint_and_then}; +use crate::utils::{is_copy, is_type_diagnostic_item}; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_path, NestedVisitorMap, Visitor}; +use rustc_hir::{self, HirId, Path}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; + +use super::OPTION_MAP_UNWRAP_OR; + +/// lint use of `map().unwrap_or()` for `Option`s +pub(super) fn lint<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &rustc_hir::Expr<'_>, + map_args: &'tcx [rustc_hir::Expr<'_>], + unwrap_args: &'tcx [rustc_hir::Expr<'_>], + map_span: Span, +) { + // lint if the caller of `map()` is an `Option` + if is_type_diagnostic_item(cx, cx.tables.expr_ty(&map_args[0]), sym!(option_type)) { + if !is_copy(cx, cx.tables.expr_ty(&unwrap_args[1])) { + // Do not lint if the `map` argument uses identifiers in the `map` + // argument that are also used in the `unwrap_or` argument + + let mut unwrap_visitor = UnwrapVisitor { + cx, + identifiers: FxHashSet::default(), + }; + unwrap_visitor.visit_expr(&unwrap_args[1]); + + let mut map_expr_visitor = MapExprVisitor { + cx, + identifiers: unwrap_visitor.identifiers, + found_identifier: false, + }; + map_expr_visitor.visit_expr(&map_args[1]); + + if map_expr_visitor.found_identifier { + return; + } + } + + if differing_macro_contexts(unwrap_args[1].span, map_span) { + return; + } + + let mut applicability = Applicability::MachineApplicable; + // get snippet for unwrap_or() + let unwrap_snippet = snippet_with_applicability(cx, unwrap_args[1].span, "..", &mut applicability); + // lint message + // comparing the snippet from source to raw text ("None") below is safe + // because we already have checked the type. + let arg = if unwrap_snippet == "None" { "None" } else { "a" }; + let unwrap_snippet_none = unwrap_snippet == "None"; + let suggest = if unwrap_snippet_none { + "and_then(f)" + } else { + "map_or(a, f)" + }; + let msg = &format!( + "called `map(f).unwrap_or({})` on an `Option` value. \ + This can be done more directly by calling `{}` instead", + arg, suggest + ); + + span_lint_and_then(cx, OPTION_MAP_UNWRAP_OR, expr.span, msg, |diag| { + let map_arg_span = map_args[1].span; + + let mut suggestion = vec![ + ( + map_span, + String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }), + ), + (expr.span.with_lo(unwrap_args[0].span.hi()), String::from("")), + ]; + + if !unwrap_snippet_none { + suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet))); + } + + diag.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability); + }); + } +} + +struct UnwrapVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + identifiers: FxHashSet, +} + +impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + self.identifiers.insert(ident(path)); + walk_path(self, path); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +struct MapExprVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + identifiers: FxHashSet, + found_identifier: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + if self.identifiers.contains(&ident(path)) { + self.found_identifier = true; + return; + } + walk_path(self, path); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +fn ident(path: &Path<'_>) -> Symbol { + path.segments + .last() + .expect("segments should be composed of at least 1 element") + .ident + .name +} diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs new file mode 100644 index 0000000000000..41c9ce7cda3e6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -0,0 +1,142 @@ +use crate::utils::paths; +use crate::utils::usage::mutated_variables; +use crate::utils::{match_qpath, match_trait_method, span_lint}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; + +use if_chain::if_chain; + +use super::UNNECESSARY_FILTER_MAP; + +pub(super) fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if !match_trait_method(cx, expr, &paths::ITERATOR) { + return; + } + + if let hir::ExprKind::Closure(_, _, body_id, ..) = args[1].kind { + let body = cx.tcx.hir().body(body_id); + let arg_id = body.params[0].pat.hir_id; + let mutates_arg = + mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id)); + + let (mut found_mapping, mut found_filtering) = check_expression(&cx, arg_id, &body.value); + + let mut return_visitor = ReturnVisitor::new(&cx, arg_id); + return_visitor.visit_expr(&body.value); + found_mapping |= return_visitor.found_mapping; + found_filtering |= return_visitor.found_filtering; + + if !found_filtering { + span_lint( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + "this `.filter_map` can be written more simply using `.map`", + ); + return; + } + + if !found_mapping && !mutates_arg { + span_lint( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + "this `.filter_map` can be written more simply using `.filter`", + ); + return; + } + } +} + +// returns (found_mapping, found_filtering) +fn check_expression<'a, 'tcx>( + cx: &'a LateContext<'a, 'tcx>, + arg_id: hir::HirId, + expr: &'tcx hir::Expr<'_>, +) -> (bool, bool) { + match &expr.kind { + hir::ExprKind::Call(ref func, ref args) => { + if_chain! { + if let hir::ExprKind::Path(ref path) = func.kind; + then { + if match_qpath(path, &paths::OPTION_SOME) { + if_chain! { + if let hir::ExprKind::Path(path) = &args[0].kind; + if let Res::Local(ref local) = cx.tables.qpath_res(path, args[0].hir_id); + then { + if arg_id == *local { + return (false, false) + } + } + } + return (true, false); + } else { + // We don't know. It might do anything. + return (true, true); + } + } + } + (true, true) + }, + hir::ExprKind::Block(ref block, _) => { + if let Some(expr) = &block.expr { + check_expression(cx, arg_id, &expr) + } else { + (false, false) + } + }, + hir::ExprKind::Match(_, arms, _) => { + let mut found_mapping = false; + let mut found_filtering = false; + for arm in *arms { + let (m, f) = check_expression(cx, arg_id, &arm.body); + found_mapping |= m; + found_filtering |= f; + } + (found_mapping, found_filtering) + }, + hir::ExprKind::Path(path) if match_qpath(path, &paths::OPTION_NONE) => (false, true), + _ => (true, true), + } +} + +struct ReturnVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + arg_id: hir::HirId, + // Found a non-None return that isn't Some(input) + found_mapping: bool, + // Found a return that isn't Some + found_filtering: bool, +} + +impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'a, 'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> { + ReturnVisitor { + cx, + arg_id, + found_mapping: false, + found_filtering: false, + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if let hir::ExprKind::Ret(Some(expr)) = &expr.kind { + let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr); + self.found_mapping |= found_mapping; + self.found_filtering |= found_filtering; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/minmax.rs b/src/tools/clippy/clippy_lints/src/minmax.rs new file mode 100644 index 0000000000000..b02c993de526b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/minmax.rs @@ -0,0 +1,102 @@ +use crate::consts::{constant_simple, Constant}; +use crate::utils::{match_def_path, paths, span_lint}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::cmp::Ordering; + +declare_clippy_lint! { + /// **What it does:** Checks for expressions where `std::cmp::min` and `max` are + /// used to clamp values, but switched so that the result is constant. + /// + /// **Why is this bad?** This is in all probability not the intended outcome. At + /// the least it hurts readability of the code. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```ignore + /// min(0, max(100, x)) + /// ``` + /// It will always be equal to `0`. Probably the author meant to clamp the value + /// between 0 and 100, but has erroneously swapped `min` and `max`. + pub MIN_MAX, + correctness, + "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant" +} + +declare_lint_pass!(MinMaxPass => [MIN_MAX]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MinMaxPass { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) { + if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) { + if outer_max == inner_max { + return; + } + match ( + outer_max, + Constant::partial_cmp(cx.tcx, cx.tables.expr_ty(ie), &outer_c, &inner_c), + ) { + (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (), + _ => { + span_lint( + cx, + MIN_MAX, + expr.span, + "this `min`/`max` combination leads to constant result", + ); + }, + } + } + } + } +} + +#[derive(PartialEq, Eq, Debug)] +enum MinMax { + Min, + Max, +} + +fn min_max<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> { + if let ExprKind::Call(ref path, ref args) = expr.kind { + if let ExprKind::Path(ref qpath) = path.kind { + cx.tables.qpath_res(qpath, path.hir_id).opt_def_id().and_then(|def_id| { + if match_def_path(cx, def_id, &paths::CMP_MIN) { + fetch_const(cx, args, MinMax::Min) + } else if match_def_path(cx, def_id, &paths::CMP_MAX) { + fetch_const(cx, args, MinMax::Max) + } else { + None + } + }) + } else { + None + } + } else { + None + } +} + +fn fetch_const<'a>( + cx: &LateContext<'_, '_>, + args: &'a [Expr<'a>], + m: MinMax, +) -> Option<(MinMax, Constant, &'a Expr<'a>)> { + if args.len() != 2 { + return None; + } + if let Some(c) = constant_simple(cx, cx.tables, &args[0]) { + if constant_simple(cx, cx.tables, &args[1]).is_none() { + // otherwise ignore + Some((m, c, &args[1])) + } else { + None + } + } else if let Some(c) = constant_simple(cx, cx.tables, &args[1]) { + Some((m, c, &args[0])) + } else { + None + } +} diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs new file mode 100644 index 0000000000000..e1d524c2231e4 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/misc.rs @@ -0,0 +1,696 @@ +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt, StmtKind, Ty, + TyKind, UnOp, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::hygiene::DesugaringKind; +use rustc_span::source_map::{ExpnKind, Span}; + +use crate::consts::{constant, Constant}; +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_integer_const, iter_input_pats, + last_path_segment, match_qpath, match_trait_method, paths, snippet, snippet_opt, span_lint, span_lint_and_sugg, + span_lint_and_then, span_lint_hir_and_then, walk_ptrs_ty, SpanlessEq, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for function arguments and let bindings denoted as + /// `ref`. + /// + /// **Why is this bad?** The `ref` declaration makes the function take an owned + /// value, but turns the argument into a reference (which means that the value + /// is destroyed when exiting the function). This adds not much value: either + /// take a reference type, or take an owned value and create references in the + /// body. + /// + /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The + /// type of `x` is more obvious with the former. + /// + /// **Known problems:** If the argument is dereferenced within the function, + /// removing the `ref` will lead to errors. This can be fixed by removing the + /// dereferences, e.g., changing `*x` to `x` within the function. + /// + /// **Example:** + /// ```rust + /// fn foo(ref x: u8) -> bool { + /// true + /// } + /// ``` + pub TOPLEVEL_REF_ARG, + style, + "an entire binding declared as `ref`, in a function argument or a `let` statement" +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons to NaN. + /// + /// **Why is this bad?** NaN does not compare meaningfully to anything – not + /// even itself – so those comparisons are simply wrong. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1.0; + /// + /// if x == f32::NAN { } + /// ``` + pub CMP_NAN, + correctness, + "comparisons to `NAN`, which will always return false, probably not intended" +} + +declare_clippy_lint! { + /// **What it does:** Checks for (in-)equality comparisons on floating-point + /// values (apart from zero), except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// **Why is this bad?** Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1.2331f64; + /// let y = 1.2332f64; + /// if y == 1.23f64 { } + /// if y != x {} // where both are floats + /// ``` + pub FLOAT_CMP, + correctness, + "using `==` or `!=` on float values instead of comparing difference with an epsilon" +} + +declare_clippy_lint! { + /// **What it does:** Checks for conversions to owned values just for the sake + /// of a comparison. + /// + /// **Why is this bad?** The comparison can operate on a reference, so creating + /// an owned value effectively throws it away directly afterwards, which is + /// needlessly consuming code and heap space. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x.to_owned() == y {} + /// ``` + /// Could be written as + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x == y {} + /// ``` + pub CMP_OWNED, + perf, + "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for getting the remainder of a division by one. + /// + /// **Why is this bad?** The result can only ever be zero. No one will write + /// such code deliberately, unless trying to win an Underhanded Rust + /// Contest. Even for that contest, it's probably a bad idea. Use something more + /// underhanded. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// let a = x % 1; + /// ``` + pub MODULO_ONE, + correctness, + "taking a number modulo 1, which always returns 0" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of bindings with a single leading + /// underscore. + /// + /// **Why is this bad?** A single leading underscore is usually used to indicate + /// that a binding will not be used. Using such a binding breaks this + /// expectation. + /// + /// **Known problems:** The lint does not work properly with desugaring and + /// macro, it has been allowed in the mean time. + /// + /// **Example:** + /// ```rust + /// let _x = 0; + /// let y = _x + 1; // Here we are using `_x`, even though it has a leading + /// // underscore. We should rename `_x` to `x` + /// ``` + pub USED_UNDERSCORE_BINDING, + pedantic, + "using a binding which is prefixed with an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of short circuit boolean conditions as + /// a + /// statement. + /// + /// **Why is this bad?** Using a short circuit boolean condition as a statement + /// may hide the fact that the second part is executed or not depending on the + /// outcome of the first part. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// f() && g(); // We should write `if f() { g(); }`. + /// ``` + pub SHORT_CIRCUIT_STATEMENT, + complexity, + "using a short circuit boolean condition as a statement" +} + +declare_clippy_lint! { + /// **What it does:** Catch casts from `0` to some pointer type + /// + /// **Why is this bad?** This generally means `null` and is better expressed as + /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let a = 0 as *const u32; + /// ``` + pub ZERO_PTR, + style, + "using `0 as *{const, mut} T`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for (in-)equality comparisons on floating-point + /// value and constant, except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// **Why is this bad?** Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x: f64 = 1.0; + /// const ONE: f64 = 1.00; + /// x == ONE; // where both are floats + /// ``` + pub FLOAT_CMP_CONST, + restriction, + "using `==` or `!=` on float constants instead of comparing difference with an epsilon" +} + +declare_lint_pass!(MiscLints => [ + TOPLEVEL_REF_ARG, + CMP_NAN, + FLOAT_CMP, + CMP_OWNED, + MODULO_ONE, + USED_UNDERSCORE_BINDING, + SHORT_CIRCUIT_STATEMENT, + ZERO_PTR, + FLOAT_CMP_CONST +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MiscLints { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + k: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + if let FnKind::Closure(_) = k { + // Does not apply to closures + return; + } + for arg in iter_input_pats(decl, body) { + match arg.pat.kind { + PatKind::Binding(BindingAnnotation::Ref, ..) | PatKind::Binding(BindingAnnotation::RefMut, ..) => { + span_lint( + cx, + TOPLEVEL_REF_ARG, + arg.pat.span, + "`ref` directly on a function argument is ignored. Consider using a reference type \ + instead.", + ); + }, + _ => {}, + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) { + if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let PatKind::Binding(an, .., name, None) = local.pat.kind; + if let Some(ref init) = local.init; + if !higher::is_from_for_desugar(local); + then { + if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut { + let sugg_init = if init.span.from_expansion() { + Sugg::hir_with_macro_callsite(cx, init, "..") + } else { + Sugg::hir(cx, init, "..") + }; + let (mutopt, initref) = if an == BindingAnnotation::RefMut { + ("mut ", sugg_init.mut_addr()) + } else { + ("", sugg_init.addr()) + }; + let tyopt = if let Some(ref ty) = local.ty { + format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, "_")) + } else { + String::new() + }; + span_lint_hir_and_then( + cx, + TOPLEVEL_REF_ARG, + init.hir_id, + local.pat.span, + "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", + |diag| { + diag.span_suggestion( + stmt.span, + "try", + format!( + "let {name}{tyopt} = {initref};", + name=snippet(cx, name.span, "_"), + tyopt=tyopt, + initref=initref, + ), + Applicability::MachineApplicable, + ); + } + ); + } + } + }; + if_chain! { + if let StmtKind::Semi(ref expr) = stmt.kind; + if let ExprKind::Binary(ref binop, ref a, ref b) = expr.kind; + if binop.node == BinOpKind::And || binop.node == BinOpKind::Or; + if let Some(sugg) = Sugg::hir_opt(cx, a); + then { + span_lint_and_then(cx, + SHORT_CIRCUIT_STATEMENT, + stmt.span, + "boolean short circuit operator in statement may be clearer using an explicit test", + |diag| { + let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg }; + diag.span_suggestion( + stmt.span, + "replace it with", + format!( + "if {} {{ {}; }}", + sugg, + &snippet(cx, b.span, ".."), + ), + Applicability::MachineApplicable, // snippet + ); + }); + } + }; + } + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + match expr.kind { + ExprKind::Cast(ref e, ref ty) => { + check_cast(cx, expr.span, e, ty); + return; + }, + ExprKind::Binary(ref cmp, ref left, ref right) => { + let op = cmp.node; + if op.is_comparison() { + check_nan(cx, left, expr); + check_nan(cx, right, expr); + check_to_owned(cx, left, right); + check_to_owned(cx, right, left); + } + if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) { + if is_allowed(cx, left) || is_allowed(cx, right) { + return; + } + + // Allow comparing the results of signum() + if is_signum(cx, left) && is_signum(cx, right) { + return; + } + + if let Some(name) = get_item_name(cx, expr) { + let name = name.as_str(); + if name == "eq" + || name == "ne" + || name == "is_nan" + || name.starts_with("eq_") + || name.ends_with("_eq") + { + return; + } + } + let is_comparing_arrays = is_array(cx, left) || is_array(cx, right); + let (lint, msg) = get_lint_and_message( + is_named_constant(cx, left) || is_named_constant(cx, right), + is_comparing_arrays, + ); + span_lint_and_then(cx, lint, expr.span, msg, |diag| { + let lhs = Sugg::hir(cx, left, ".."); + let rhs = Sugg::hir(cx, right, ".."); + + if !is_comparing_arrays { + diag.span_suggestion( + expr.span, + "consider comparing them within some error", + format!( + "({}).abs() {} error", + lhs - rhs, + if op == BinOpKind::Eq { '<' } else { '>' } + ), + Applicability::HasPlaceholders, // snippet + ); + } + diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error`"); + }); + } else if op == BinOpKind::Rem && is_integer_const(cx, right, 1) { + span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); + } + }, + _ => {}, + } + if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) { + // Don't lint things expanded by #[derive(...)], etc or `await` desugaring + return; + } + let binding = match expr.kind { + ExprKind::Path(ref qpath) => { + let binding = last_path_segment(qpath).ident.as_str(); + if binding.starts_with('_') && + !binding.starts_with("__") && + binding != "_result" && // FIXME: #944 + is_used(cx, expr) && + // don't lint if the declaration is in a macro + non_macro_local(cx, cx.tables.qpath_res(qpath, expr.hir_id)) + { + Some(binding) + } else { + None + } + }, + ExprKind::Field(_, ident) => { + let name = ident.as_str(); + if name.starts_with('_') && !name.starts_with("__") { + Some(name) + } else { + None + } + }, + _ => None, + }; + if let Some(binding) = binding { + span_lint( + cx, + USED_UNDERSCORE_BINDING, + expr.span, + &format!( + "used binding `{}` which is prefixed with an underscore. A leading \ + underscore signals that a binding will not be used.", + binding + ), + ); + } + } +} + +fn get_lint_and_message( + is_comparing_constants: bool, + is_comparing_arrays: bool, +) -> (&'static rustc_lint::Lint, &'static str) { + if is_comparing_constants { + ( + FLOAT_CMP_CONST, + if is_comparing_arrays { + "strict comparison of `f32` or `f64` constant arrays" + } else { + "strict comparison of `f32` or `f64` constant" + }, + ) + } else { + ( + FLOAT_CMP, + if is_comparing_arrays { + "strict comparison of `f32` or `f64` arrays" + } else { + "strict comparison of `f32` or `f64`" + }, + ) + } +} + +fn check_nan(cx: &LateContext<'_, '_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) { + if_chain! { + if !in_constant(cx, cmp_expr.hir_id); + if let Some((value, _)) = constant(cx, cx.tables, expr); + then { + let needs_lint = match value { + Constant::F32(num) => num.is_nan(), + Constant::F64(num) => num.is_nan(), + _ => false, + }; + + if needs_lint { + span_lint( + cx, + CMP_NAN, + cmp_expr.span, + "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead", + ); + } + } + } +} + +fn is_named_constant<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> bool { + if let Some((_, res)) = constant(cx, cx.tables, expr) { + res + } else { + false + } +} + +fn is_allowed<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> bool { + match constant(cx, cx.tables, expr) { + Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(), + Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(), + Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f { + Constant::F32(f) => *f == 0.0 || (*f).is_infinite(), + Constant::F64(f) => *f == 0.0 || (*f).is_infinite(), + _ => false, + }), + _ => false, + } +} + +// Return true if `expr` is the result of `signum()` invoked on a float value. +fn is_signum(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + // The negation of a signum is still a signum + if let ExprKind::Unary(UnOp::UnNeg, ref child_expr) = expr.kind { + return is_signum(cx, &child_expr); + } + + if_chain! { + if let ExprKind::MethodCall(ref method_name, _, ref expressions) = expr.kind; + if sym!(signum) == method_name.ident.name; + // Check that the receiver of the signum() is a float (expressions[0] is the receiver of + // the method call) + then { + return is_float(cx, &expressions[0]); + } + } + false +} + +fn is_float(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + let value = &walk_ptrs_ty(cx.tables.expr_ty(expr)).kind; + + if let ty::Array(arr_ty, _) = value { + return matches!(arr_ty.kind, ty::Float(_)); + }; + + matches!(value, ty::Float(_)) +} + +fn is_array(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + matches!(&walk_ptrs_ty(cx.tables.expr_ty(expr)).kind, ty::Array(_, _)) +} + +fn check_to_owned(cx: &LateContext<'_, '_>, expr: &Expr<'_>, other: &Expr<'_>) { + let (arg_ty, snip) = match expr.kind { + ExprKind::MethodCall(.., ref args) if args.len() == 1 => { + if match_trait_method(cx, expr, &paths::TO_STRING) || match_trait_method(cx, expr, &paths::TO_OWNED) { + (cx.tables.expr_ty_adjusted(&args[0]), snippet(cx, args[0].span, "..")) + } else { + return; + } + }, + ExprKind::Call(ref path, ref v) if v.len() == 1 => { + if let ExprKind::Path(ref path) = path.kind { + if match_qpath(path, &["String", "from_str"]) || match_qpath(path, &["String", "from"]) { + (cx.tables.expr_ty_adjusted(&v[0]), snippet(cx, v[0].span, "..")) + } else { + return; + } + } else { + return; + } + }, + _ => return, + }; + + let other_ty = cx.tables.expr_ty_adjusted(other); + let partial_eq_trait_id = match cx.tcx.lang_items().eq_trait() { + Some(id) => id, + None => return, + }; + + let deref_arg_impl_partial_eq_other = arg_ty.builtin_deref(true).map_or(false, |tam| { + implements_trait(cx, tam.ty, partial_eq_trait_id, &[other_ty.into()]) + }); + let arg_impl_partial_eq_deref_other = other_ty.builtin_deref(true).map_or(false, |tam| { + implements_trait(cx, arg_ty, partial_eq_trait_id, &[tam.ty.into()]) + }); + let arg_impl_partial_eq_other = implements_trait(cx, arg_ty, partial_eq_trait_id, &[other_ty.into()]); + + if !deref_arg_impl_partial_eq_other && !arg_impl_partial_eq_deref_other && !arg_impl_partial_eq_other { + return; + } + + let other_gets_derefed = match other.kind { + ExprKind::Unary(UnOp::UnDeref, _) => true, + _ => false, + }; + + let lint_span = if other_gets_derefed { + expr.span.to(other.span) + } else { + expr.span + }; + + span_lint_and_then( + cx, + CMP_OWNED, + lint_span, + "this creates an owned instance just for comparison", + |diag| { + // This also catches `PartialEq` implementations that call `to_owned`. + if other_gets_derefed { + diag.span_label(lint_span, "try implementing the comparison without allocating"); + return; + } + + let try_hint = if deref_arg_impl_partial_eq_other { + // suggest deref on the left + format!("*{}", snip) + } else { + // suggest dropping the to_owned on the left + snip.to_string() + }; + + diag.span_suggestion( + lint_span, + "try", + try_hint, + Applicability::MachineApplicable, // snippet + ); + }, + ); +} + +/// Heuristic to see if an expression is used. Should be compatible with +/// `unused_variables`'s idea +/// of what it means for an expression to be "used". +fn is_used(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, expr) { + match parent.kind { + ExprKind::Assign(_, ref rhs, _) | ExprKind::AssignOp(_, _, ref rhs) => { + SpanlessEq::new(cx).eq_expr(rhs, expr) + }, + _ => is_used(cx, parent), + } + } else { + true + } +} + +/// Tests whether an expression is in a macro expansion (e.g., something +/// generated by `#[derive(...)]` or the like). +fn in_attributes_expansion(expr: &Expr<'_>) -> bool { + use rustc_span::hygiene::MacroKind; + if expr.span.from_expansion() { + let data = expr.span.ctxt().outer_expn_data(); + + if let ExpnKind::Macro(MacroKind::Attr, _) = data.kind { + true + } else { + false + } + } else { + false + } +} + +/// Tests whether `res` is a variable defined outside a macro. +fn non_macro_local(cx: &LateContext<'_, '_>, res: def::Res) -> bool { + if let def::Res::Local(id) = res { + !cx.tcx.hir().span(id).from_expansion() + } else { + false + } +} + +fn check_cast(cx: &LateContext<'_, '_>, span: Span, e: &Expr<'_>, ty: &Ty<'_>) { + if_chain! { + if let TyKind::Ptr(ref mut_ty) = ty.kind; + if let ExprKind::Lit(ref lit) = e.kind; + if let LitKind::Int(0, _) = lit.node; + if !in_constant(cx, e.hir_id); + then { + let (msg, sugg_fn) = match mut_ty.mutbl { + Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"), + Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"), + }; + + let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind { + (format!("{}()", sugg_fn), Applicability::MachineApplicable) + } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) { + (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable) + } else { + // `MaybeIncorrect` as type inference may not work with the suggested code + (format!("{}()", sugg_fn), Applicability::MaybeIncorrect) + }; + span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/misc_early.rs b/src/tools/clippy/clippy_lints/src/misc_early.rs new file mode 100644 index 0000000000000..adfd8dfb1c18a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/misc_early.rs @@ -0,0 +1,638 @@ +use crate::utils::{ + constants, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, + span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_ast::ast::{ + BindingMode, Block, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability, + NodeId, Pat, PatKind, StmtKind, UnOp, +}; +use rustc_ast::visit::{walk_expr, FnKind, Visitor}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for structure field patterns bound to wildcards. + /// + /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on + /// the fields that are actually bound. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// let { a: _, b: ref b, c: _ } = .. + /// ``` + pub UNNEEDED_FIELD_PATTERN, + restriction, + "struct fields bound to a wildcard instead of using `..`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for function arguments having the similar names + /// differing by an underscore. + /// + /// **Why is this bad?** It affects code readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo(a: i32, _a: i32) {} + /// ``` + pub DUPLICATE_UNDERSCORE_ARGUMENT, + style, + "function arguments having names which only differ by an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Detects closures called in the same expression where they + /// are defined. + /// + /// **Why is this bad?** It is unnecessarily adding to the expression's + /// complexity. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// (|| 42)() + /// ``` + pub REDUNDANT_CLOSURE_CALL, + complexity, + "throwaway closures called in the expression they are defined" +} + +declare_clippy_lint! { + /// **What it does:** Detects expressions of the form `--x`. + /// + /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was + /// decremented. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut x = 3; + /// --x; + /// ``` + pub DOUBLE_NEG, + style, + "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++" +} + +declare_clippy_lint! { + /// **What it does:** Warns on hexadecimal literals with mixed-case letter + /// digits. + /// + /// **Why is this bad?** It looks confusing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let y = 0x1a9BAcD; + /// ``` + pub MIXED_CASE_HEX_LITERALS, + style, + "hex literals whose letter digits are not consistently upper- or lowercased" +} + +declare_clippy_lint! { + /// **What it does:** Warns if literal suffixes are not separated by an + /// underscore. + /// + /// **Why is this bad?** It is much less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let y = 123832i32; + /// ``` + pub UNSEPARATED_LITERAL_SUFFIX, + pedantic, + "literals whose suffix is not separated by an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Warns if an integral constant literal starts with `0`. + /// + /// **Why is this bad?** In some languages (including the infamous C language + /// and most of its + /// family), this marks an octal constant. In Rust however, this is a decimal + /// constant. This could + /// be confusing for both the writer and a reader of the constant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// In Rust: + /// ```rust + /// fn main() { + /// let a = 0123; + /// println!("{}", a); + /// } + /// ``` + /// + /// prints `123`, while in C: + /// + /// ```c + /// #include + /// + /// int main() { + /// int a = 0123; + /// printf("%d\n", a); + /// } + /// ``` + /// + /// prints `83` (as `83 == 0o123` while `123 == 0o173`). + pub ZERO_PREFIXED_LITERAL, + complexity, + "integer literals starting with `0`" +} + +declare_clippy_lint! { + /// **What it does:** Warns if a generic shadows a built-in type. + /// + /// **Why is this bad?** This gives surprising type errors. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```ignore + /// impl Foo { + /// fn impl_func(&self) -> u32 { + /// 42 + /// } + /// } + /// ``` + pub BUILTIN_TYPE_SHADOW, + style, + "shadowing a builtin type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for patterns in the form `name @ _`. + /// + /// **Why is this bad?** It's almost always more readable to just use direct + /// bindings. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v = Some("abc"); + /// + /// match v { + /// Some(x) => (), + /// y @ _ => (), // easier written as `y`, + /// } + /// ``` + pub REDUNDANT_PATTERN, + style, + "using `name @ _` in a pattern" +} + +declare_clippy_lint! { + /// **What it does:** Checks for tuple patterns with a wildcard + /// pattern (`_`) is next to a rest pattern (`..`). + /// + /// _NOTE_: While `_, ..` means there is at least one element left, `..` + /// means there are 0 or more elements left. This can make a difference + /// when refactoring, but shouldn't result in errors in the refactored code, + /// since the wildcard pattern isn't used anyway. + /// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern + /// can match that element as well. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct TupleStruct(u32, u32, u32); + /// # let t = TupleStruct(1, 2, 3); + /// + /// match t { + /// TupleStruct(0, .., _) => (), + /// _ => (), + /// } + /// ``` + /// can be written as + /// ```rust + /// # struct TupleStruct(u32, u32, u32); + /// # let t = TupleStruct(1, 2, 3); + /// + /// match t { + /// TupleStruct(0, ..) => (), + /// _ => (), + /// } + /// ``` + pub UNNEEDED_WILDCARD_PATTERN, + complexity, + "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" +} + +declare_lint_pass!(MiscEarlyLints => [ + UNNEEDED_FIELD_PATTERN, + DUPLICATE_UNDERSCORE_ARGUMENT, + REDUNDANT_CLOSURE_CALL, + DOUBLE_NEG, + MIXED_CASE_HEX_LITERALS, + UNSEPARATED_LITERAL_SUFFIX, + ZERO_PREFIXED_LITERAL, + BUILTIN_TYPE_SHADOW, + REDUNDANT_PATTERN, + UNNEEDED_WILDCARD_PATTERN, +]); + +// Used to find `return` statements or equivalents e.g., `?` +struct ReturnVisitor { + found_return: bool, +} + +impl ReturnVisitor { + #[must_use] + fn new() -> Self { + Self { found_return: false } + } +} + +impl<'ast> Visitor<'ast> for ReturnVisitor { + fn visit_expr(&mut self, ex: &'ast Expr) { + if let ExprKind::Ret(_) = ex.kind { + self.found_return = true; + } else if let ExprKind::Try(_) = ex.kind { + self.found_return = true; + } + + walk_expr(self, ex) + } +} + +impl EarlyLintPass for MiscEarlyLints { + fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) { + for param in &gen.params { + if let GenericParamKind::Type { .. } = param.kind { + let name = param.ident.as_str(); + if constants::BUILTIN_TYPES.contains(&&*name) { + span_lint( + cx, + BUILTIN_TYPE_SHADOW, + param.ident.span, + &format!("This generic shadows the built-in type `{}`", name), + ); + } + } + } + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind { + let mut wilds = 0; + let type_name = npat + .segments + .last() + .expect("A path must have at least one segment") + .ident + .name; + + for field in pfields { + if let PatKind::Wild = field.pat.kind { + wilds += 1; + } + } + if !pfields.is_empty() && wilds == pfields.len() { + span_lint_and_help( + cx, + UNNEEDED_FIELD_PATTERN, + pat.span, + "All the struct fields are matched to a wildcard pattern, consider using `..`.", + None, + &format!("Try with `{} {{ .. }}` instead", type_name), + ); + return; + } + if wilds > 0 { + for field in pfields { + if let PatKind::Wild = field.pat.kind { + wilds -= 1; + if wilds > 0 { + span_lint( + cx, + UNNEEDED_FIELD_PATTERN, + field.span, + "You matched a field with a wildcard pattern. Consider using `..` instead", + ); + } else { + let mut normal = vec![]; + + for field in pfields { + match field.pat.kind { + PatKind::Wild => {}, + _ => { + if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) { + normal.push(n); + } + }, + } + } + + span_lint_and_help( + cx, + UNNEEDED_FIELD_PATTERN, + field.span, + "You matched a field with a wildcard pattern. Consider using `..` \ + instead", + None, + &format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")), + ); + } + } + } + } + } + + if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind { + let left_binding = match left { + BindingMode::ByRef(Mutability::Mut) => "ref mut ", + BindingMode::ByRef(Mutability::Not) => "ref ", + _ => "", + }; + + if let PatKind::Wild = right.kind { + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN, + pat.span, + &format!( + "the `{} @ _` pattern can be written as just `{}`", + ident.name, ident.name, + ), + "try", + format!("{}{}", left_binding, ident.name), + Applicability::MachineApplicable, + ); + } + } + + check_unneeded_wildcard_pattern(cx, pat); + } + + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { + let mut registered_names: FxHashMap = FxHashMap::default(); + + for arg in &fn_kind.decl().inputs { + if let PatKind::Ident(_, ident, None) = arg.pat.kind { + let arg_name = ident.to_string(); + + if arg_name.starts_with('_') { + if let Some(correspondence) = registered_names.get(&arg_name[1..]) { + span_lint( + cx, + DUPLICATE_UNDERSCORE_ARGUMENT, + *correspondence, + &format!( + "`{}` already exists, having another argument having almost the same \ + name makes code comprehension and documentation more difficult", + arg_name[1..].to_owned() + ), + ); + } + } else { + registered_names.insert(arg_name, arg.pat.span); + } + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + match expr.kind { + ExprKind::Call(ref paren, _) => { + if let ExprKind::Paren(ref closure) = paren.kind { + if let ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.kind { + let mut visitor = ReturnVisitor::new(); + visitor.visit_expr(block); + if !visitor.found_return { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE_CALL, + expr.span, + "Try not to call a closure in the expression where it is declared.", + |diag| { + if decl.inputs.is_empty() { + let mut app = Applicability::MachineApplicable; + let hint = + snippet_with_applicability(cx, block.span, "..", &mut app).into_owned(); + diag.span_suggestion(expr.span, "Try doing something like: ", hint, app); + } + }, + ); + } + } + } + }, + ExprKind::Unary(UnOp::Neg, ref inner) => { + if let ExprKind::Unary(UnOp::Neg, _) = inner.kind { + span_lint( + cx, + DOUBLE_NEG, + expr.span, + "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op", + ); + } + }, + ExprKind::Lit(ref lit) => Self::check_lit(cx, lit), + _ => (), + } + } + + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { + for w in block.stmts.windows(2) { + if_chain! { + if let StmtKind::Local(ref local) = w[0].kind; + if let Option::Some(ref t) = local.init; + if let ExprKind::Closure(..) = t.kind; + if let PatKind::Ident(_, ident, _) = local.pat.kind; + if let StmtKind::Semi(ref second) = w[1].kind; + if let ExprKind::Assign(_, ref call, _) = second.kind; + if let ExprKind::Call(ref closure, _) = call.kind; + if let ExprKind::Path(_, ref path) = closure.kind; + then { + if ident == path.segments[0].ident { + span_lint( + cx, + REDUNDANT_CLOSURE_CALL, + second.span, + "Closure called just once immediately after it was declared", + ); + } + } + } + } + } +} + +impl MiscEarlyLints { + fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) { + // We test if first character in snippet is a number, because the snippet could be an expansion + // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`. + // Note that this check also covers special case that `line!()` is eagerly expanded by compiler. + // See for a regression. + // FIXME: Find a better way to detect those cases. + let lit_snip = match snippet_opt(cx, lit.span) { + Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip, + _ => return, + }; + + if let LitKind::Int(value, lit_int_type) = lit.kind { + let suffix = match lit_int_type { + LitIntType::Signed(ty) => ty.name_str(), + LitIntType::Unsigned(ty) => ty.name_str(), + LitIntType::Unsuffixed => "", + }; + + let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { + val + } else { + return; // It's useless so shouldn't lint. + }; + // Do not lint when literal is unsuffixed. + if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { + span_lint_and_sugg( + cx, + UNSEPARATED_LITERAL_SUFFIX, + lit.span, + "integer type suffix should be separated by an underscore", + "add an underscore", + format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), + Applicability::MachineApplicable, + ); + } + + if lit_snip.starts_with("0x") { + if maybe_last_sep_idx <= 2 { + // It's meaningless or causes range error. + return; + } + let mut seen = (false, false); + for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() { + match ch { + b'a'..=b'f' => seen.0 = true, + b'A'..=b'F' => seen.1 = true, + _ => {}, + } + if seen.0 && seen.1 { + span_lint( + cx, + MIXED_CASE_HEX_LITERALS, + lit.span, + "inconsistent casing in hexadecimal literal", + ); + break; + } + } + } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") { + /* nothing to do */ + } else if value != 0 && lit_snip.starts_with('0') { + span_lint_and_then( + cx, + ZERO_PREFIXED_LITERAL, + lit.span, + "this is a decimal constant", + |diag| { + diag.span_suggestion( + lit.span, + "if you mean to use a decimal constant, remove the `0` to avoid confusion", + lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + lit.span, + "if you mean to use an octal constant, use `0o`", + format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind { + let suffix = float_ty.name_str(); + let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { + val + } else { + return; // It's useless so shouldn't lint. + }; + if lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { + span_lint_and_sugg( + cx, + UNSEPARATED_LITERAL_SUFFIX, + lit.span, + "float type suffix should be separated by an underscore", + "add an underscore", + format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), + Applicability::MachineApplicable, + ); + } + } + } +} + +fn check_unneeded_wildcard_pattern(cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind { + fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) { + span_lint_and_sugg( + cx, + UNNEEDED_WILDCARD_PATTERN, + span, + if only_one { + "this pattern is unneeded as the `..` pattern can match that element" + } else { + "these patterns are unneeded as the `..` pattern can match those elements" + }, + if only_one { "remove it" } else { "remove them" }, + "".to_string(), + Applicability::MachineApplicable, + ); + } + + #[allow(clippy::trivially_copy_pass_by_ref)] + fn is_wild>(pat: &&P) -> bool { + if let PatKind::Wild = pat.kind { + true + } else { + false + } + } + + if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) { + if let Some((left_index, left_pat)) = patterns[..rest_index] + .iter() + .rev() + .take_while(is_wild) + .enumerate() + .last() + { + span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0); + } + + if let Some((right_index, right_pat)) = + patterns[rest_index + 1..].iter().take_while(is_wild).enumerate().last() + { + span_lint( + cx, + patterns[rest_index].span.shrink_to_hi().to(right_pat.span), + right_index == 0, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs new file mode 100644 index 0000000000000..4301157e16440 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs @@ -0,0 +1,145 @@ +use crate::utils::{fn_has_unsatisfiable_preds, has_drop, is_entrypoint_fn, span_lint, trait_ref_of_method}; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_mir::transform::qualify_min_const_fn::is_min_const_fn; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; +use rustc_typeck::hir_ty_to_ty; + +declare_clippy_lint! { + /// **What it does:** + /// + /// Suggests the use of `const` in functions and methods where possible. + /// + /// **Why is this bad?** + /// + /// Not having the function const prevents callers of the function from being const as well. + /// + /// **Known problems:** + /// + /// Const functions are currently still being worked on, with some features only being available + /// on nightly. This lint does not consider all edge cases currently and the suggestions may be + /// incorrect if you are using this lint on stable. + /// + /// Also, the lint only runs one pass over the code. Consider these two non-const functions: + /// + /// ```rust + /// fn a() -> i32 { + /// 0 + /// } + /// fn b() -> i32 { + /// a() + /// } + /// ``` + /// + /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time + /// can't be const as it calls a non-const function. Making `a` const and running Clippy again, + /// will suggest to make `b` const, too. + /// + /// **Example:** + /// + /// ```rust + /// # struct Foo { + /// # random_number: usize, + /// # } + /// # impl Foo { + /// fn new() -> Self { + /// Self { random_number: 42 } + /// } + /// # } + /// ``` + /// + /// Could be a const fn: + /// + /// ```rust + /// # struct Foo { + /// # random_number: usize, + /// # } + /// # impl Foo { + /// const fn new() -> Self { + /// Self { random_number: 42 } + /// } + /// # } + /// ``` + pub MISSING_CONST_FOR_FN, + nursery, + "Lint functions definitions that could be made `const fn`" +} + +declare_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingConstForFn { + fn check_fn( + &mut self, + cx: &LateContext<'_, '_>, + kind: FnKind<'_>, + _: &FnDecl<'_>, + _: &Body<'_>, + span: Span, + hir_id: HirId, + ) { + let def_id = cx.tcx.hir().local_def_id(hir_id); + + if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) { + return; + } + + // Building MIR for `fn`s with unsatisfiable preds results in ICE. + if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { + return; + } + + // Perform some preliminary checks that rule out constness on the Clippy side. This way we + // can skip the actual const check and return early. + match kind { + FnKind::ItemFn(_, generics, header, ..) => { + let has_const_generic_params = generics + .params + .iter() + .any(|param| matches!(param.kind, GenericParamKind::Const{ .. })); + + if already_const(header) || has_const_generic_params { + return; + } + }, + FnKind::Method(_, sig, ..) => { + if trait_ref_of_method(cx, hir_id).is_some() + || already_const(sig.header) + || method_accepts_dropable(cx, sig.decl.inputs) + { + return; + } + }, + _ => return, + } + + let mir = cx.tcx.optimized_mir(def_id); + + if let Err((span, err)) = is_min_const_fn(cx.tcx, def_id.to_def_id(), &mir) { + if rustc_mir::const_eval::is_min_const_fn(cx.tcx, def_id.to_def_id()) { + cx.tcx.sess.span_err(span, &err); + } + } else { + span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); + } + } +} + +/// Returns true if any of the method parameters is a type that implements `Drop`. The method +/// can't be made const then, because `drop` can't be const-evaluated. +fn method_accepts_dropable(cx: &LateContext<'_, '_>, param_tys: &[hir::Ty<'_>]) -> bool { + // If any of the params are dropable, return true + param_tys.iter().any(|hir_ty| { + let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty); + has_drop(cx, ty_ty) + }) +} + +// We don't have to lint on something that's already `const` +#[must_use] +fn already_const(header: hir::FnHeader) -> bool { + header.constness == Constness::Const +} diff --git a/src/tools/clippy/clippy_lints/src/missing_doc.rs b/src/tools/clippy/clippy_lints/src/missing_doc.rs new file mode 100644 index 0000000000000..2eefb6bbaf424 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/missing_doc.rs @@ -0,0 +1,204 @@ +// Note: More specifically this lint is largely inspired (aka copied) from +// *rustc*'s +// [`missing_doc`]. +// +// [`missing_doc`]: https://github.com/rust-lang/rust/blob/d6d05904697d89099b55da3331155392f1db9c00/src/librustc_lint/builtin.rs#L246 +// + +use crate::utils::span_lint; +use if_chain::if_chain; +use rustc_ast::ast::{self, MetaItem, MetaItemKind}; +use rustc_ast::attr; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Warns if there is missing doc for any documentable item + /// (public or private). + /// + /// **Why is this bad?** Doc is good. *rustc* has a `MISSING_DOCS` + /// allowed-by-default lint for + /// public members, but has no way to enforce documentation of private items. + /// This lint fixes that. + /// + /// **Known problems:** None. + pub MISSING_DOCS_IN_PRIVATE_ITEMS, + restriction, + "detects missing documentation for public and private members" +} + +pub struct MissingDoc { + /// Stack of whether #[doc(hidden)] is set + /// at each level which has lint attributes. + doc_hidden_stack: Vec, +} + +impl Default for MissingDoc { + #[must_use] + fn default() -> Self { + Self::new() + } +} + +impl MissingDoc { + #[must_use] + pub fn new() -> Self { + Self { + doc_hidden_stack: vec![false], + } + } + + fn doc_hidden(&self) -> bool { + *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") + } + + fn has_include(meta: Option) -> bool { + if_chain! { + if let Some(meta) = meta; + if let MetaItemKind::List(list) = meta.kind; + if let Some(meta) = list.get(0); + if let Some(name) = meta.ident(); + then { + name.as_str() == "include" + } else { + false + } + } + } + + fn check_missing_docs_attrs( + &self, + cx: &LateContext<'_, '_>, + attrs: &[ast::Attribute], + sp: Span, + desc: &'static str, + ) { + // If we're building a test harness, then warning about + // documentation is probably not really relevant right now. + if cx.sess().opts.test { + return; + } + + // `#[doc(hidden)]` disables missing_docs check. + if self.doc_hidden() { + return; + } + + if sp.from_expansion() { + return; + } + + let has_doc = attrs + .iter() + .any(|a| a.is_doc_comment() || a.doc_str().is_some() || a.is_value_str() || Self::has_include(a.meta())); + if !has_doc { + span_lint( + cx, + MISSING_DOCS_IN_PRIVATE_ITEMS, + sp, + &format!("missing documentation for {}", desc), + ); + } + } +} + +impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingDoc { + fn enter_lint_attrs(&mut self, _: &LateContext<'a, 'tcx>, attrs: &'tcx [ast::Attribute]) { + let doc_hidden = self.doc_hidden() + || attrs.iter().any(|attr| { + attr.check_name(sym!(doc)) + && match attr.meta_item_list() { + None => false, + Some(l) => attr::list_contains_name(&l[..], sym!(hidden)), + } + }); + self.doc_hidden_stack.push(doc_hidden); + } + + fn exit_lint_attrs(&mut self, _: &LateContext<'a, 'tcx>, _: &'tcx [ast::Attribute]) { + self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); + } + + fn check_crate(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx hir::Crate<'_>) { + self.check_missing_docs_attrs(cx, &krate.item.attrs, krate.item.span, "crate"); + } + + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, it: &'tcx hir::Item<'_>) { + let desc = match it.kind { + hir::ItemKind::Const(..) => "a constant", + hir::ItemKind::Enum(..) => "an enum", + hir::ItemKind::Fn(..) => { + // ignore main() + if it.ident.name == sym!(main) { + let def_id = it.hir_id.owner; + let def_key = cx.tcx.hir().def_key(def_id); + if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) { + return; + } + } + "a function" + }, + hir::ItemKind::Mod(..) => "a module", + hir::ItemKind::Static(..) => "a static", + hir::ItemKind::Struct(..) => "a struct", + hir::ItemKind::Trait(..) => "a trait", + hir::ItemKind::TraitAlias(..) => "a trait alias", + hir::ItemKind::TyAlias(..) => "a type alias", + hir::ItemKind::Union(..) => "a union", + hir::ItemKind::OpaqueTy(..) => "an existential type", + hir::ItemKind::ExternCrate(..) + | hir::ItemKind::ForeignMod(..) + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::Impl { .. } + | hir::ItemKind::Use(..) => return, + }; + + self.check_missing_docs_attrs(cx, &it.attrs, it.span, desc); + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, trait_item: &'tcx hir::TraitItem<'_>) { + let desc = match trait_item.kind { + hir::TraitItemKind::Const(..) => "an associated constant", + hir::TraitItemKind::Fn(..) => "a trait method", + hir::TraitItemKind::Type(..) => "an associated type", + }; + + self.check_missing_docs_attrs(cx, &trait_item.attrs, trait_item.span, desc); + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + // If the method is an impl for a trait, don't doc. + let def_id = cx.tcx.hir().local_def_id(impl_item.hir_id); + match cx.tcx.associated_item(def_id).container { + ty::TraitContainer(_) => return, + ty::ImplContainer(cid) => { + if cx.tcx.impl_trait_ref(cid).is_some() { + return; + } + }, + } + + let desc = match impl_item.kind { + hir::ImplItemKind::Const(..) => "an associated constant", + hir::ImplItemKind::Fn(..) => "a method", + hir::ImplItemKind::TyAlias(_) => "an associated type", + hir::ImplItemKind::OpaqueTy(_) => "an existential type", + }; + self.check_missing_docs_attrs(cx, &impl_item.attrs, impl_item.span, desc); + } + + fn check_struct_field(&mut self, cx: &LateContext<'a, 'tcx>, sf: &'tcx hir::StructField<'_>) { + if !sf.is_positional() { + self.check_missing_docs_attrs(cx, &sf.attrs, sf.span, "a struct field"); + } + } + + fn check_variant(&mut self, cx: &LateContext<'a, 'tcx>, v: &'tcx hir::Variant<'_>) { + self.check_missing_docs_attrs(cx, &v.attrs, v.span, "a variant"); + } +} diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs new file mode 100644 index 0000000000000..5300fd2215b39 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -0,0 +1,164 @@ +use crate::utils::span_lint; +use rustc_ast::ast; +use rustc_hir as hir; +use rustc_lint::{self, LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** it lints if an exported function, method, trait method with default impl, + /// or trait method impl is not `#[inline]`. + /// + /// **Why is this bad?** In general, it is not. Functions can be inlined across + /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled, + /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates + /// might intend for most of the methods in their public API to be able to be inlined across + /// crates even when LTO is disabled. For these types of crates, enabling this lint might make + /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and + /// then opt out for specific methods where this might not make sense. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// pub fn foo() {} // missing #[inline] + /// fn ok() {} // ok + /// #[inline] pub fn bar() {} // ok + /// #[inline(always)] pub fn baz() {} // ok + /// + /// pub trait Bar { + /// fn bar(); // ok + /// fn def_bar() {} // missing #[inline] + /// } + /// + /// struct Baz; + /// impl Baz { + /// fn private() {} // ok + /// } + /// + /// impl Bar for Baz { + /// fn bar() {} // ok - Baz is not exported + /// } + /// + /// pub struct PubBaz; + /// impl PubBaz { + /// fn private() {} // ok + /// pub fn not_ptrivate() {} // missing #[inline] + /// } + /// + /// impl Bar for PubBaz { + /// fn bar() {} // missing #[inline] + /// fn def_bar() {} // missing #[inline] + /// } + /// ``` + pub MISSING_INLINE_IN_PUBLIC_ITEMS, + restriction, + "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" +} + +fn check_missing_inline_attrs(cx: &LateContext<'_, '_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) { + let has_inline = attrs.iter().any(|a| a.check_name(sym!(inline))); + if !has_inline { + span_lint( + cx, + MISSING_INLINE_IN_PUBLIC_ITEMS, + sp, + &format!("missing `#[inline]` for {}", desc), + ); + } +} + +fn is_executable(cx: &LateContext<'_, '_>) -> bool { + use rustc_session::config::CrateType; + + cx.tcx.sess.crate_types.get().iter().any(|t: &CrateType| match t { + CrateType::Executable => true, + _ => false, + }) +} + +declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingInline { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, it: &'tcx hir::Item<'_>) { + if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable(cx) { + return; + } + + if !cx.access_levels.is_exported(it.hir_id) { + return; + } + match it.kind { + hir::ItemKind::Fn(..) => { + let desc = "a function"; + check_missing_inline_attrs(cx, &it.attrs, it.span, desc); + }, + hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, ref _bounds, trait_items) => { + // note: we need to check if the trait is exported so we can't use + // `LateLintPass::check_trait_item` here. + for tit in trait_items { + let tit_ = cx.tcx.hir().trait_item(tit.id); + match tit_.kind { + hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {}, + hir::TraitItemKind::Fn(..) => { + if tit.defaultness.has_value() { + // trait method with default body needs inline in case + // an impl is not provided + let desc = "a default trait method"; + let item = cx.tcx.hir().expect_trait_item(tit.id.hir_id); + check_missing_inline_attrs(cx, &item.attrs, item.span, desc); + } + }, + } + } + }, + hir::ItemKind::Const(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::Static(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::ExternCrate(..) + | hir::ItemKind::ForeignMod(..) + | hir::ItemKind::Impl { .. } + | hir::ItemKind::Use(..) => {}, + }; + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + use rustc_middle::ty::{ImplContainer, TraitContainer}; + if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable(cx) { + return; + } + + // If the item being implemented is not exported, then we don't need #[inline] + if !cx.access_levels.is_exported(impl_item.hir_id) { + return; + } + + let desc = match impl_item.kind { + hir::ImplItemKind::Fn(..) => "a method", + hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(_) | hir::ImplItemKind::OpaqueTy(_) => return, + }; + + let def_id = cx.tcx.hir().local_def_id(impl_item.hir_id); + let trait_def_id = match cx.tcx.associated_item(def_id).container { + TraitContainer(cid) => Some(cid), + ImplContainer(cid) => cx.tcx.impl_trait_ref(cid).map(|t| t.def_id), + }; + + if let Some(trait_def_id) = trait_def_id { + if trait_def_id.is_local() && !cx.access_levels.is_exported(impl_item.hir_id) { + // If a trait is being implemented for an item, and the + // trait is not exported, we don't need #[inline] + return; + } + } + + check_missing_inline_attrs(cx, &impl_item.attrs, impl_item.span, desc); + } +} diff --git a/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs b/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs new file mode 100644 index 0000000000000..4ca90455bc4d1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs @@ -0,0 +1,148 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{sext, span_lint_and_then}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::fmt::Display; + +declare_clippy_lint! { + /// **What it does:** Checks for modulo arithemtic. + /// + /// **Why is this bad?** The results of modulo (%) operation might differ + /// depending on the language, when negative numbers are involved. + /// If you interop with different languages it might be beneficial + /// to double check all places that use modulo arithmetic. + /// + /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = -17 % 3; + /// ``` + pub MODULO_ARITHMETIC, + restriction, + "any modulo arithmetic statement" +} + +declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]); + +struct OperandInfo { + string_representation: Option, + is_negative: bool, + is_integral: bool, +} + +fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { + match constant(cx, cx.tables, operand) { + Some((Constant::Int(v), _)) => match cx.tables.expr_ty(expr).kind { + ty::Int(ity) => { + let value = sext(cx.tcx, v, ity); + return Some(OperandInfo { + string_representation: Some(value.to_string()), + is_negative: value < 0, + is_integral: true, + }); + }, + ty::Uint(_) => { + return Some(OperandInfo { + string_representation: None, + is_negative: false, + is_integral: true, + }); + }, + _ => {}, + }, + Some((Constant::F32(f), _)) => { + return Some(floating_point_operand_info(&f)); + }, + Some((Constant::F64(f), _)) => { + return Some(floating_point_operand_info(&f)); + }, + _ => {}, + } + None +} + +fn floating_point_operand_info>(f: &T) -> OperandInfo { + OperandInfo { + string_representation: Some(format!("{:.3}", *f)), + is_negative: *f < 0.0.into(), + is_integral: false, + } +} + +fn might_have_negative_value(t: &ty::TyS<'_>) -> bool { + t.is_signed() || t.is_floating_point() +} + +fn check_const_operands<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx Expr<'_>, + lhs_operand: &OperandInfo, + rhs_operand: &OperandInfo, +) { + if lhs_operand.is_negative ^ rhs_operand.is_negative { + span_lint_and_then( + cx, + MODULO_ARITHMETIC, + expr.span, + &format!( + "you are using modulo operator on constants with different signs: `{} % {}`", + lhs_operand.string_representation.as_ref().unwrap(), + rhs_operand.string_representation.as_ref().unwrap() + ), + |diag| { + diag.note("double check for expected result especially when interoperating with different languages"); + if lhs_operand.is_integral { + diag.note("or consider using `rem_euclid` or similar function"); + } + }, + ); + } +} + +fn check_non_const_operands<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, operand: &Expr<'_>) { + let operand_type = cx.tables.expr_ty(operand); + if might_have_negative_value(operand_type) { + span_lint_and_then( + cx, + MODULO_ARITHMETIC, + expr.span, + "you are using modulo operator on types that might have different signs", + |diag| { + diag.note("double check for expected result especially when interoperating with different languages"); + if operand_type.is_integral() { + diag.note("or consider using `rem_euclid` or similar function"); + } + }, + ); + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ModuloArithmetic { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + match &expr.kind { + ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => { + if let BinOpKind::Rem = op.node { + let lhs_operand = analyze_operand(lhs, cx, expr); + let rhs_operand = analyze_operand(rhs, cx, expr); + if_chain! { + if let Some(lhs_operand) = lhs_operand; + if let Some(rhs_operand) = rhs_operand; + then { + check_const_operands(cx, expr, &lhs_operand, &rhs_operand); + } + else { + check_non_const_operands(cx, expr, lhs); + } + } + }; + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/multiple_crate_versions.rs b/src/tools/clippy/clippy_lints/src/multiple_crate_versions.rs new file mode 100644 index 0000000000000..ed85d0315bd25 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/multiple_crate_versions.rs @@ -0,0 +1,68 @@ +//! lint on multiple versions of a crate being used + +use crate::utils::{run_lints, span_lint}; +use rustc_hir::{Crate, CRATE_HIR_ID}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::DUMMY_SP; + +use itertools::Itertools; + +declare_clippy_lint! { + /// **What it does:** Checks to see if multiple versions of a crate are being + /// used. + /// + /// **Why is this bad?** This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// **Known problems:** Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// + /// **Example:** + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + pub MULTIPLE_CRATE_VERSIONS, + cargo, + "multiple versions of the same crate being used" +} + +declare_lint_pass!(MultipleCrateVersions => [MULTIPLE_CRATE_VERSIONS]); + +impl LateLintPass<'_, '_> for MultipleCrateVersions { + fn check_crate(&mut self, cx: &LateContext<'_, '_>, _: &Crate<'_>) { + if !run_lints(cx, &[MULTIPLE_CRATE_VERSIONS], CRATE_HIR_ID) { + return; + } + + let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().exec() { + metadata + } else { + span_lint(cx, MULTIPLE_CRATE_VERSIONS, DUMMY_SP, "could not read cargo metadata"); + + return; + }; + + let mut packages = metadata.packages; + packages.sort_by(|a, b| a.name.cmp(&b.name)); + + for (name, group) in &packages.into_iter().group_by(|p| p.name.clone()) { + let group: Vec = group.collect(); + + if group.len() > 1 { + let versions = group.into_iter().map(|p| p.version).join(", "); + + span_lint( + cx, + MULTIPLE_CRATE_VERSIONS, + DUMMY_SP, + &format!("multiple versions for dependency `{}`: {}", name, versions), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/mut_key.rs b/src/tools/clippy/clippy_lints/src/mut_key.rs new file mode 100644 index 0000000000000..0b9b7e1b8cc1b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_key.rs @@ -0,0 +1,124 @@ +use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method, walk_ptrs_ty}; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{Adt, Array, RawPtr, Ref, Slice, Tuple, Ty, TypeAndMut}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for sets/maps with mutable key types. + /// + /// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and + /// `BtreeSet` rely on either the hash or the order of keys be unchanging, + /// so having types with interior mutability is a bad idea. + /// + /// **Known problems:** We don't currently account for `Rc` or `Arc`, so + /// this may yield false positives. + /// + /// **Example:** + /// ```rust + /// use std::cmp::{PartialEq, Eq}; + /// use std::collections::HashSet; + /// use std::hash::{Hash, Hasher}; + /// use std::sync::atomic::AtomicUsize; + ///# #[allow(unused)] + /// + /// struct Bad(AtomicUsize); + /// impl PartialEq for Bad { + /// fn eq(&self, rhs: &Self) -> bool { + /// .. + /// ; unimplemented!(); + /// } + /// } + /// + /// impl Eq for Bad {} + /// + /// impl Hash for Bad { + /// fn hash(&self, h: &mut H) { + /// .. + /// ; unimplemented!(); + /// } + /// } + /// + /// fn main() { + /// let _: HashSet = HashSet::new(); + /// } + /// ``` + pub MUTABLE_KEY_TYPE, + correctness, + "Check for mutable `Map`/`Set` key type" +} + +declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MutableKeyType { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'tcx>) { + if let hir::ItemKind::Fn(ref sig, ..) = item.kind { + check_sig(cx, item.hir_id, &sig.decl); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'tcx>) { + if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind { + if trait_ref_of_method(cx, item.hir_id).is_none() { + check_sig(cx, item.hir_id, &sig.decl); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'tcx>) { + if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind { + check_sig(cx, item.hir_id, &sig.decl); + } + } + + fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &hir::Local<'_>) { + if let hir::PatKind::Wild = local.pat.kind { + return; + } + check_ty(cx, local.span, cx.tables.pat_ty(&*local.pat)); + } +} + +fn check_sig<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) { + let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id); + let fn_sig = cx.tcx.fn_sig(fn_def_id); + for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) { + check_ty(cx, hir_ty.span, ty); + } + check_ty( + cx, + decl.output.span(), + cx.tcx.erase_late_bound_regions(&fn_sig.output()), + ); +} + +// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased +// generics (because the compiler cannot ensure immutability for unknown types). +fn check_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, ty: Ty<'tcx>) { + let ty = walk_ptrs_ty(ty); + if let Adt(def, substs) = ty.kind { + if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET] + .iter() + .any(|path| match_def_path(cx, def.did, &**path)) + && is_mutable_type(cx, substs.type_at(0), span) + { + span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type"); + } + } +} + +fn is_mutable_type<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>, span: Span) -> bool { + match ty.kind { + RawPtr(TypeAndMut { ty: inner_ty, mutbl }) | Ref(_, inner_ty, mutbl) => { + mutbl == hir::Mutability::Mut || is_mutable_type(cx, inner_ty, span) + }, + Slice(inner_ty) => is_mutable_type(cx, inner_ty, span), + Array(inner_ty, size) => { + size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_mutable_type(cx, inner_ty, span) + }, + Tuple(..) => ty.tuple_fields().any(|ty| is_mutable_type(cx, ty, span)), + Adt(..) => cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx, cx.param_env, span), + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/mut_mut.rs b/src/tools/clippy/clippy_lints/src/mut_mut.rs new file mode 100644 index 0000000000000..f7a20a74b85e2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_mut.rs @@ -0,0 +1,114 @@ +use crate::utils::{higher, span_lint}; +use rustc_hir as hir; +use rustc_hir::intravisit; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for instances of `mut mut` references. + /// + /// **Why is this bad?** Multiple `mut`s don't add anything meaningful to the + /// source. This is either a copy'n'paste error, or it shows a fundamental + /// misunderstanding of references. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let mut y = 1; + /// let x = &mut &mut y; + /// ``` + pub MUT_MUT, + pedantic, + "usage of double-mut refs, e.g., `&mut &mut ...`" +} + +declare_lint_pass!(MutMut => [MUT_MUT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MutMut { + fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx hir::Block<'_>) { + intravisit::walk_block(&mut MutVisitor { cx }, block); + } + + fn check_ty(&mut self, cx: &LateContext<'a, 'tcx>, ty: &'tcx hir::Ty<'_>) { + use rustc_hir::intravisit::Visitor; + + MutVisitor { cx }.visit_ty(ty); + } +} + +pub struct MutVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if in_external_macro(self.cx.sess(), expr.span) { + return; + } + + if let Some((_, arg, body)) = higher::for_loop(expr) { + // A `for` loop lowers to: + // ```rust + // match ::std::iter::Iterator::next(&mut iter) { + // // ^^^^ + // ``` + // Let's ignore the generated code. + intravisit::walk_expr(self, arg); + intravisit::walk_expr(self, body); + } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, ref e) = expr.kind { + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { + span_lint( + self.cx, + MUT_MUT, + expr.span, + "generally you want to avoid `&mut &mut _` if possible", + ); + } else if let ty::Ref(_, _, hir::Mutability::Mut) = self.cx.tables.expr_ty(e).kind { + span_lint( + self.cx, + MUT_MUT, + expr.span, + "this expression mutably borrows a mutable reference. Consider reborrowing", + ); + } + } + } + + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) { + if let hir::TyKind::Rptr( + _, + hir::MutTy { + ty: ref pty, + mutbl: hir::Mutability::Mut, + }, + ) = ty.kind + { + if let hir::TyKind::Rptr( + _, + hir::MutTy { + mutbl: hir::Mutability::Mut, + .. + }, + ) = pty.kind + { + span_lint( + self.cx, + MUT_MUT, + ty.span, + "generally you want to avoid `&mut &mut _` if possible", + ); + } + } + + intravisit::walk_ty(self, ty); + } + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs new file mode 100644 index 0000000000000..e5680482e5bfb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -0,0 +1,82 @@ +use crate::utils::span_lint; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::subst::Subst; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Detects giving a mutable reference to a function that only + /// requires an immutable reference. + /// + /// **Why is this bad?** The immutable reference rules out all other references + /// to the value. Also the code misleads about the intent of the call site. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// my_vec.push(&mut value) + /// ``` + pub UNNECESSARY_MUT_PASSED, + style, + "an argument passed as a mutable reference although the callee only demands an immutable reference" +} + +declare_lint_pass!(UnnecessaryMutPassed => [UNNECESSARY_MUT_PASSED]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnecessaryMutPassed { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + match e.kind { + ExprKind::Call(ref fn_expr, ref arguments) => { + if let ExprKind::Path(ref path) = fn_expr.kind { + check_arguments( + cx, + arguments, + cx.tables.expr_ty(fn_expr), + &rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)), + ); + } + }, + ExprKind::MethodCall(ref path, _, ref arguments) => { + let def_id = cx.tables.type_dependent_def_id(e.hir_id).unwrap(); + let substs = cx.tables.node_substs(e.hir_id); + let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs); + check_arguments(cx, arguments, method_type, &path.ident.as_str()) + }, + _ => (), + } + } +} + +fn check_arguments<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + arguments: &[Expr<'_>], + type_definition: Ty<'tcx>, + name: &str, +) { + match type_definition.kind { + ty::FnDef(..) | ty::FnPtr(_) => { + let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); + for (argument, parameter) in arguments.iter().zip(parameters.iter()) { + match parameter.kind { + ty::Ref(_, _, Mutability::Not) + | ty::RawPtr(ty::TypeAndMut { + mutbl: Mutability::Not, .. + }) => { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind { + span_lint( + cx, + UNNECESSARY_MUT_PASSED, + argument.span, + &format!("The function/method `{}` doesn't need a mutable reference", name), + ); + } + }, + _ => (), + } + } + }, + _ => (), + } +} diff --git a/src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs b/src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs new file mode 100644 index 0000000000000..119e0905ff442 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs @@ -0,0 +1,159 @@ +use crate::utils::{is_direct_expn_of, span_lint}; +use if_chain::if_chain; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability, StmtKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for function/method calls with a mutable + /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros. + /// + /// **Why is this bad?** In release builds `debug_assert!` macros are optimized out by the + /// compiler. + /// Therefore mutating something in a `debug_assert!` macro results in different behaviour + /// between a release and debug build. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust,ignore + /// debug_assert_eq!(vec![3].pop(), Some(3)); + /// // or + /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() } + /// debug_assert!(take_a_mut_parameter(&mut 5)); + /// ``` + pub DEBUG_ASSERT_WITH_MUT_CALL, + nursery, + "mutable arguments in `debug_assert{,_ne,_eq}!`" +} + +declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]); + +const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"]; + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DebugAssertWithMutCall { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + for dmn in &DEBUG_MACRO_NAMES { + if is_direct_expn_of(e.span, dmn).is_some() { + if let Some(span) = extract_call(cx, e) { + span_lint( + cx, + DEBUG_ASSERT_WITH_MUT_CALL, + span, + &format!("do not call a function with mutable arguments inside of `{}!`", dmn), + ); + } + } + } + } +} + +//HACK(hellow554): remove this when #4694 is implemented +fn extract_call<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) -> Option { + if_chain! { + if let ExprKind::Block(ref block, _) = e.kind; + if block.stmts.len() == 1; + if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind; + then { + // debug_assert + if_chain! { + if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind; + if let ExprKind::DropTemps(ref droptmp) = ifclause.kind; + if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind; + then { + let mut visitor = MutArgVisitor::new(cx); + visitor.visit_expr(condition); + return visitor.expr_span(); + } + } + + // debug_assert_{eq,ne} + if_chain! { + if let ExprKind::Block(ref matchblock, _) = matchexpr.kind; + if let Some(ref matchheader) = matchblock.expr; + if let ExprKind::Match(ref headerexpr, _, _) = matchheader.kind; + if let ExprKind::Tup(ref conditions) = headerexpr.kind; + if conditions.len() == 2; + then { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref lhs) = conditions[0].kind { + let mut visitor = MutArgVisitor::new(cx); + visitor.visit_expr(lhs); + if let Some(span) = visitor.expr_span() { + return Some(span); + } + } + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref rhs) = conditions[1].kind { + let mut visitor = MutArgVisitor::new(cx); + visitor.visit_expr(rhs); + if let Some(span) = visitor.expr_span() { + return Some(span); + } + } + } + } + } + } + + None +} + +struct MutArgVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + expr_span: Option, + found: bool, +} + +impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'a, 'tcx>) -> Self { + Self { + cx, + expr_span: None, + found: false, + } + } + + fn expr_span(&self) -> Option { + if self.found { + self.expr_span + } else { + None + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match expr.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { + self.found = true; + return; + }, + ExprKind::Path(_) => { + if let Some(adj) = self.cx.tables.adjustments().get(expr.hir_id) { + if adj + .iter() + .any(|a| matches!(a.target.kind, ty::Ref(_, _, Mutability::Mut))) + { + self.found = true; + return; + } + } + }, + // Don't check await desugars + ExprKind::Match(_, _, MatchSource::AwaitDesugar) => return, + _ if !self.found => self.expr_span = Some(expr.span), + _ => return, + } + walk_expr(self, expr) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/mutex_atomic.rs b/src/tools/clippy/clippy_lints/src/mutex_atomic.rs new file mode 100644 index 0000000000000..4e1a8be4892e6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mutex_atomic.rs @@ -0,0 +1,88 @@ +//! Checks for uses of mutex where an atomic value could be used +//! +//! This lint is **warn** by default + +use crate::utils::{is_type_diagnostic_item, span_lint}; +use rustc_ast::ast; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `Mutex` where an atomic will do. + /// + /// **Why is this bad?** Using a mutex just to make access to a plain bool or + /// reference sequential is shooting flies with cannons. + /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and + /// faster. + /// + /// **Known problems:** This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// + /// **Example:** + /// ```rust + /// # use std::sync::Mutex; + /// # let y = 1; + /// let x = Mutex::new(&y); + /// ``` + pub MUTEX_ATOMIC, + perf, + "using a mutex where an atomic value could be used instead" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `Mutex` where `X` is an integral + /// type. + /// + /// **Why is this bad?** Using a mutex just to make access to a plain integer + /// sequential is + /// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster. + /// + /// **Known problems:** This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// + /// **Example:** + /// ```rust + /// # use std::sync::Mutex; + /// let x = Mutex::new(0usize); + /// ``` + pub MUTEX_INTEGER, + nursery, + "using a mutex for an integer type" +} + +declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Mutex { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + let ty = cx.tables.expr_ty(expr); + if let ty::Adt(_, subst) = ty.kind { + if is_type_diagnostic_item(cx, ty, sym!(mutex_type)) { + let mutex_param = subst.type_at(0); + if let Some(atomic_name) = get_atomic_name(mutex_param) { + let msg = format!( + "Consider using an `{}` instead of a `Mutex` here. If you just want the locking \ + behavior and not the internal type, consider using `Mutex<()>`.", + atomic_name + ); + match mutex_param.kind { + ty::Uint(t) if t != ast::UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg), + ty::Int(t) if t != ast::IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg), + _ => span_lint(cx, MUTEX_ATOMIC, expr.span, &msg), + }; + } + } + } + } +} + +fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { + match ty.kind { + ty::Bool => Some("AtomicBool"), + ty::Uint(_) => Some("AtomicUsize"), + ty::Int(_) => Some("AtomicIsize"), + ty::RawPtr(_) => Some("AtomicPtr"), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs new file mode 100644 index 0000000000000..efa77db822dd0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -0,0 +1,348 @@ +//! Checks for needless boolean results of if-else expressions +//! +//! This lint is **warn** by default + +use crate::utils::sugg::Sugg; +use crate::utils::{higher, parent_node_is_if_expr, snippet_with_applicability, span_lint, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for expressions of the form `if c { true } else { + /// false }` + /// (or vice versa) and suggest using the condition directly. + /// + /// **Why is this bad?** Redundant code. + /// + /// **Known problems:** Maybe false positives: Sometimes, the two branches are + /// painstakingly documented (which we, of course, do not detect), so they *may* + /// have some value. Even then, the documentation can be rewritten to match the + /// shorter code. + /// + /// **Example:** + /// ```rust,ignore + /// if x { + /// false + /// } else { + /// true + /// } + /// ``` + /// Could be written as + /// ```rust,ignore + /// !x + /// ``` + pub NEEDLESS_BOOL, + complexity, + "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expressions of the form `x == true`, + /// `x != true` and order comparisons such as `x < true` (or vice versa) and + /// suggest using the variable directly. + /// + /// **Why is this bad?** Unnecessary code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// if x == true {} + /// if y == false {} + /// ``` + /// use `x` directly: + /// ```rust,ignore + /// if x {} + /// if !y {} + /// ``` + pub BOOL_COMPARISON, + complexity, + "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" +} + +declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessBool { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + use self::Expression::{Bool, RetBool}; + if let Some((ref pred, ref then_block, Some(ref else_expr))) = higher::if_block(&e) { + let reduce = |ret, not| { + let mut applicability = Applicability::MachineApplicable; + let snip = Sugg::hir_with_applicability(cx, pred, "", &mut applicability); + let mut snip = if not { !snip } else { snip }; + + if ret { + snip = snip.make_return(); + } + + if parent_node_is_if_expr(&e, &cx) { + snip = snip.blockify() + } + + span_lint_and_sugg( + cx, + NEEDLESS_BOOL, + e.span, + "this if-then-else expression returns a bool literal", + "you can reduce it to", + snip.to_string(), + applicability, + ); + }; + if let ExprKind::Block(ref then_block, _) = then_block.kind { + match (fetch_bool_block(then_block), fetch_bool_expr(else_expr)) { + (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => { + span_lint( + cx, + NEEDLESS_BOOL, + e.span, + "this if-then-else expression will always return true", + ); + }, + (RetBool(false), RetBool(false)) | (Bool(false), Bool(false)) => { + span_lint( + cx, + NEEDLESS_BOOL, + e.span, + "this if-then-else expression will always return false", + ); + }, + (RetBool(true), RetBool(false)) => reduce(true, false), + (Bool(true), Bool(false)) => reduce(false, false), + (RetBool(false), RetBool(true)) => reduce(true, true), + (Bool(false), Bool(true)) => reduce(false, true), + _ => (), + } + } else { + panic!("IfExpr `then` node is not an `ExprKind::Block`"); + } + } + } +} + +declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BoolComparison { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind { + let ignore_case = None::<(fn(_) -> _, &str)>; + let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; + match node { + BinOpKind::Eq => { + let true_case = Some((|h| h, "equality checks against true are unnecessary")); + let false_case = Some(( + |h: Sugg<'_>| !h, + "equality checks against false can be replaced by a negation", + )); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal) + }, + BinOpKind::Ne => { + let true_case = Some(( + |h: Sugg<'_>| !h, + "inequality checks against true can be replaced by a negation", + )); + let false_case = Some((|h| h, "inequality checks against false are unnecessary")); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal) + }, + BinOpKind::Lt => check_comparison( + cx, + e, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |h: Sugg<'_>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + Some(( + |l: Sugg<'_>, r: Sugg<'_>| (!l).bit_and(&r), + "order comparisons between booleans can be simplified", + )), + ), + BinOpKind::Gt => check_comparison( + cx, + e, + Some(( + |h: Sugg<'_>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |l: Sugg<'_>, r: Sugg<'_>| l.bit_and(&(!r)), + "order comparisons between booleans can be simplified", + )), + ), + _ => (), + } + } + } +} + +struct ExpressionInfoWithSpan { + one_side_is_unary_not: bool, + left_span: Span, + right_span: Span, +} + +fn is_unary_not(e: &Expr<'_>) -> (bool, Span) { + if_chain! { + if let ExprKind::Unary(unop, operand) = e.kind; + if let UnOp::UnNot = unop; + then { + return (true, operand.span); + } + }; + (false, e.span) +} + +fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan { + let left = is_unary_not(left_side); + let right = is_unary_not(right_side); + + ExpressionInfoWithSpan { + one_side_is_unary_not: left.0 != right.0, + left_span: left.1, + right_span: right.1, + } +} + +fn check_comparison<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + e: &'tcx Expr<'_>, + left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, + no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &str)>, +) { + use self::Expression::{Bool, Other}; + + if let ExprKind::Binary(op, ref left_side, ref right_side) = e.kind { + let (l_ty, r_ty) = (cx.tables.expr_ty(left_side), cx.tables.expr_ty(right_side)); + if l_ty.is_bool() && r_ty.is_bool() { + let mut applicability = Applicability::MachineApplicable; + + if let BinOpKind::Eq = op.node { + let expression_info = one_side_is_unary_not(&left_side, &right_side); + if expression_info.one_side_is_unary_not { + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + e.span, + "This comparison might be written more concisely", + "try simplifying it as shown", + format!( + "{} != {}", + snippet_with_applicability(cx, expression_info.left_span, "..", &mut applicability), + snippet_with_applicability(cx, expression_info.right_span, "..", &mut applicability) + ), + applicability, + ) + } + } + + match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { + (Bool(true), Other) => left_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, right_side, applicability, m, h) + }), + (Other, Bool(true)) => right_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, left_side, applicability, m, h) + }), + (Bool(false), Other) => left_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, right_side, applicability, m, h) + }), + (Other, Bool(false)) => right_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, e, left_side, applicability, m, h) + }), + (Other, Other) => no_literal.map_or((), |(h, m)| { + let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); + let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + e.span, + m, + "try simplifying it as shown", + h(left_side, right_side).to_string(), + applicability, + ) + }), + _ => (), + } + } + } +} + +fn suggest_bool_comparison<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + e: &'tcx Expr<'_>, + expr: &Expr<'_>, + mut applicability: Applicability, + message: &str, + conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, +) { + let hint = Sugg::hir_with_applicability(cx, expr, "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + e.span, + message, + "try simplifying it as shown", + conv_hint(hint).to_string(), + applicability, + ); +} + +enum Expression { + Bool(bool), + RetBool(bool), + Other, +} + +fn fetch_bool_block(block: &Block<'_>) -> Expression { + match (&*block.stmts, block.expr.as_ref()) { + (&[], Some(e)) => fetch_bool_expr(&**e), + (&[ref e], None) => { + if let StmtKind::Semi(ref e) = e.kind { + if let ExprKind::Ret(_) = e.kind { + fetch_bool_expr(&**e) + } else { + Expression::Other + } + } else { + Expression::Other + } + }, + _ => Expression::Other, + } +} + +fn fetch_bool_expr(expr: &Expr<'_>) -> Expression { + match expr.kind { + ExprKind::Block(ref block, _) => fetch_bool_block(block), + ExprKind::Lit(ref lit_ptr) => { + if let LitKind::Bool(value) = lit_ptr.node { + Expression::Bool(value) + } else { + Expression::Other + } + }, + ExprKind::Ret(Some(ref expr)) => match fetch_bool_expr(expr) { + Expression::Bool(value) => Expression::RetBool(value), + _ => Expression::Other, + }, + _ => Expression::Other, + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_borrow.rs b/src/tools/clippy/clippy_lints/src/needless_borrow.rs new file mode 100644 index 0000000000000..9ee875d7516eb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_borrow.rs @@ -0,0 +1,124 @@ +//! Checks for needless address of operations (`&`) +//! +//! This lint is **warn** by default + +use crate::utils::{snippet_opt, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BindingAnnotation, BorrowKind, Expr, ExprKind, HirId, Item, Mutability, Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, Adjustment}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for address of operations (`&`) that are going to + /// be dereferenced immediately by the compiler. + /// + /// **Why is this bad?** Suggests that the receiver of the expression borrows + /// the expression. + /// + /// **Example:** + /// ```rust + /// let x: &i32 = &&&&&&5; + /// ``` + /// + /// **Known problems:** None. + pub NEEDLESS_BORROW, + nursery, + "taking a reference that is going to be automatically dereferenced" +} + +#[derive(Default)] +pub struct NeedlessBorrow { + derived_item: Option, +} + +impl_lint_pass!(NeedlessBorrow => [NEEDLESS_BORROW]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessBorrow { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() || self.derived_item.is_some() { + return; + } + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = e.kind { + if let ty::Ref(..) = cx.tables.expr_ty(inner).kind { + for adj3 in cx.tables.expr_adjustments(e).windows(3) { + if let [Adjustment { + kind: Adjust::Deref(_), .. + }, Adjustment { + kind: Adjust::Deref(_), .. + }, Adjustment { + kind: Adjust::Borrow(_), + .. + }] = *adj3 + { + span_lint_and_then( + cx, + NEEDLESS_BORROW, + e.span, + "this expression borrows a reference that is immediately dereferenced \ + by the compiler", + |diag| { + if let Some(snippet) = snippet_opt(cx, inner.span) { + diag.span_suggestion( + e.span, + "change this to", + snippet, + Applicability::MachineApplicable, + ); + } + }, + ); + } + } + } + } + } + fn check_pat(&mut self, cx: &LateContext<'a, 'tcx>, pat: &'tcx Pat<'_>) { + if pat.span.from_expansion() || self.derived_item.is_some() { + return; + } + if_chain! { + if let PatKind::Binding(BindingAnnotation::Ref, .., name, _) = pat.kind; + if let ty::Ref(_, tam, mutbl) = cx.tables.pat_ty(pat).kind; + if mutbl == Mutability::Not; + if let ty::Ref(_, _, mutbl) = tam.kind; + // only lint immutable refs, because borrowed `&mut T` cannot be moved out + if mutbl == Mutability::Not; + then { + span_lint_and_then( + cx, + NEEDLESS_BORROW, + pat.span, + "this pattern creates a reference to a reference", + |diag| { + if let Some(snippet) = snippet_opt(cx, name.span) { + diag.span_suggestion( + pat.span, + "change this to", + snippet, + Applicability::MachineApplicable, + ); + } + } + ) + } + } + } + + fn check_item(&mut self, _: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if item.attrs.iter().any(|a| a.check_name(sym!(automatically_derived))) { + debug_assert!(self.derived_item.is_none()); + self.derived_item = Some(item.hir_id); + } + } + + fn check_item_post(&mut self, _: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let Some(id) = self.derived_item { + if item.hir_id == id { + self.derived_item = None; + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_borrowed_ref.rs b/src/tools/clippy/clippy_lints/src/needless_borrowed_ref.rs new file mode 100644 index 0000000000000..e56489c6d434d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_borrowed_ref.rs @@ -0,0 +1,92 @@ +//! Checks for useless borrowed references. +//! +//! This lint is **warn** by default + +use crate::utils::{snippet_with_applicability, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BindingAnnotation, Mutability, Node, Pat, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for useless borrowed references. + /// + /// **Why is this bad?** It is mostly useless and make the code look more + /// complex than it + /// actually is. + /// + /// **Known problems:** It seems that the `&ref` pattern is sometimes useful. + /// For instance in the following snippet: + /// ```rust,ignore + /// enum Animal { + /// Cat(u64), + /// Dog(u64), + /// } + /// + /// fn foo(a: &Animal, b: &Animal) { + /// match (a, b) { + /// (&Animal::Cat(v), k) | (k, &Animal::Cat(v)) => (), // lifetime mismatch error + /// (&Animal::Dog(ref c), &Animal::Dog(_)) => () + /// } + /// } + /// ``` + /// There is a lifetime mismatch error for `k` (indeed a and b have distinct + /// lifetime). + /// This can be fixed by using the `&ref` pattern. + /// However, the code can also be fixed by much cleaner ways + /// + /// **Example:** + /// ```rust + /// let mut v = Vec::::new(); + /// let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + /// ``` + /// This closure takes a reference on something that has been matched as a + /// reference and + /// de-referenced. + /// As such, it could just be |a| a.is_empty() + pub NEEDLESS_BORROWED_REFERENCE, + complexity, + "taking a needless borrowed reference" +} + +declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessBorrowedRef { + fn check_pat(&mut self, cx: &LateContext<'a, 'tcx>, pat: &'tcx Pat<'_>) { + if pat.span.from_expansion() { + // OK, simple enough, lints doesn't check in macro. + return; + } + + if_chain! { + // Only lint immutable refs, because `&mut ref T` may be useful. + if let PatKind::Ref(ref sub_pat, Mutability::Not) = pat.kind; + + // Check sub_pat got a `ref` keyword (excluding `ref mut`). + if let PatKind::Binding(BindingAnnotation::Ref, .., spanned_name, _) = sub_pat.kind; + let parent_id = cx.tcx.hir().get_parent_node(pat.hir_id); + if let Some(parent_node) = cx.tcx.hir().find(parent_id); + then { + // do not recurse within patterns, as they may have other references + // XXXManishearth we can relax this constraint if we only check patterns + // with a single ref pattern inside them + if let Node::Pat(_) = parent_node { + return; + } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, pat.span, + "this pattern takes a reference on something that is being de-referenced", + |diag| { + let hint = snippet_with_applicability(cx, spanned_name.span, "..", &mut applicability).into_owned(); + diag.span_suggestion( + pat.span, + "try removing the `&ref` part and just keep", + hint, + applicability, + ); + }); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_continue.rs b/src/tools/clippy/clippy_lints/src/needless_continue.rs new file mode 100644 index 0000000000000..28183810df489 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_continue.rs @@ -0,0 +1,463 @@ +//! Checks for continue statements in loops that are redundant. +//! +//! For example, the lint would catch +//! +//! ```rust +//! let mut a = 1; +//! let x = true; +//! +//! while a < 5 { +//! a = 6; +//! if x { +//! // ... +//! } else { +//! continue; +//! } +//! println!("Hello, world"); +//! } +//! ``` +//! +//! And suggest something like this: +//! +//! ```rust +//! let mut a = 1; +//! let x = true; +//! +//! while a < 5 { +//! a = 6; +//! if x { +//! // ... +//! println!("Hello, world"); +//! } +//! } +//! ``` +//! +//! This lint is **warn** by default. +use rustc_ast::ast; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{original_sp, DUMMY_SP}; +use rustc_span::Span; + +use crate::utils::{indent_of, snippet, snippet_block, span_lint_and_help}; + +declare_clippy_lint! { + /// **What it does:** The lint checks for `if`-statements appearing in loops + /// that contain a `continue` statement in either their main blocks or their + /// `else`-blocks, when omitting the `else`-block possibly with some + /// rearrangement of code can make the code easier to understand. + /// + /// **Why is this bad?** Having explicit `else` blocks for `if` statements + /// containing `continue` in their THEN branch adds unnecessary branching and + /// nesting to the code. Having an else block containing just `continue` can + /// also be better written by grouping the statements following the whole `if` + /// statement within the THEN block and omitting the else block completely. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # fn condition() -> bool { false } + /// # fn update_condition() {} + /// # let x = false; + /// while condition() { + /// update_condition(); + /// if x { + /// // ... + /// } else { + /// continue; + /// } + /// println!("Hello, world"); + /// } + /// ``` + /// + /// Could be rewritten as + /// + /// ```rust + /// # fn condition() -> bool { false } + /// # fn update_condition() {} + /// # let x = false; + /// while condition() { + /// update_condition(); + /// if x { + /// // ... + /// println!("Hello, world"); + /// } + /// } + /// ``` + /// + /// As another example, the following code + /// + /// ```rust + /// # fn waiting() -> bool { false } + /// loop { + /// if waiting() { + /// continue; + /// } else { + /// // Do something useful + /// } + /// # break; + /// } + /// ``` + /// Could be rewritten as + /// + /// ```rust + /// # fn waiting() -> bool { false } + /// loop { + /// if waiting() { + /// continue; + /// } + /// // Do something useful + /// # break; + /// } + /// ``` + pub NEEDLESS_CONTINUE, + pedantic, + "`continue` statements that can be replaced by a rearrangement of code" +} + +declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]); + +impl EarlyLintPass for NeedlessContinue { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + if !expr.span.from_expansion() { + check_and_warn(cx, expr); + } + } +} + +/* This lint has to mainly deal with two cases of needless continue + * statements. */ +// Case 1 [Continue inside else block]: +// +// loop { +// // region A +// if cond { +// // region B +// } else { +// continue; +// } +// // region C +// } +// +// This code can better be written as follows: +// +// loop { +// // region A +// if cond { +// // region B +// // region C +// } +// } +// +// Case 2 [Continue inside then block]: +// +// loop { +// // region A +// if cond { +// continue; +// // potentially more code here. +// } else { +// // region B +// } +// // region C +// } +// +// +// This snippet can be refactored to: +// +// loop { +// // region A +// if !cond { +// // region B +// // region C +// } +// } +// + +/// Given an expression, returns true if either of the following is true +/// +/// - The expression is a `continue` node. +/// - The expression node is a block with the first statement being a +/// `continue`. +fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool { + match else_expr.kind { + ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label), + ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()), + _ => false, + } +} + +fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool { + block.stmts.get(0).map_or(false, |stmt| match stmt.kind { + ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { + if let ast::ExprKind::Continue(ref l) = e.kind { + compare_labels(label, l.as_ref()) + } else { + false + } + }, + _ => false, + }) +} + +/// If the `continue` has a label, check it matches the label of the loop. +fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool { + match (loop_label, continue_label) { + // `loop { continue; }` or `'a loop { continue; }` + (_, None) => true, + // `loop { continue 'a; }` + (None, _) => false, + // `'a loop { continue 'a; }` or `'a loop { continue 'b; }` + (Some(x), Some(y)) => x.ident == y.ident, + } +} + +/// If `expr` is a loop expression (while/while let/for/loop), calls `func` with +/// the AST object representing the loop block of `expr`. +fn with_loop_block(expr: &ast::Expr, mut func: F) +where + F: FnMut(&ast::Block, Option<&ast::Label>), +{ + if let ast::ExprKind::While(_, loop_block, label) + | ast::ExprKind::ForLoop(_, _, loop_block, label) + | ast::ExprKind::Loop(loop_block, label) = &expr.kind + { + func(loop_block, label.as_ref()); + } +} + +/// If `stmt` is an if expression node with an `else` branch, calls func with +/// the +/// following: +/// +/// - The `if` expression itself, +/// - The `if` condition expression, +/// - The `then` block, and +/// - The `else` expression. +fn with_if_expr(stmt: &ast::Stmt, mut func: F) +where + F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr), +{ + match stmt.kind { + ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { + if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.kind { + func(e, cond, if_block, else_expr); + } + }, + _ => {}, + } +} + +/// A type to distinguish between the two distinct cases this lint handles. +#[derive(Copy, Clone, Debug)] +enum LintType { + ContinueInsideElseBlock, + ContinueInsideThenBlock, +} + +/// Data we pass around for construction of help messages. +struct LintData<'a> { + /// The `if` expression encountered in the above loop. + if_expr: &'a ast::Expr, + /// The condition expression for the above `if`. + if_cond: &'a ast::Expr, + /// The `then` block of the `if` statement. + if_block: &'a ast::Block, + /// The `else` block of the `if` statement. + /// Note that we only work with `if` exprs that have an `else` branch. + else_expr: &'a ast::Expr, + /// The 0-based index of the `if` statement in the containing loop block. + stmt_idx: usize, + /// The statements of the loop block. + block_stmts: &'a [ast::Stmt], +} + +const MSG_REDUNDANT_ELSE_BLOCK: &str = "this `else` block is redundant"; + +const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "there is no need for an explicit `else` block for this `if` \ + expression"; + +const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "consider dropping the `else` clause and merging the code that \ + follows (in the loop) with the `if` block"; + +const DROP_ELSE_BLOCK_MSG: &str = "consider dropping the `else` clause"; + +fn emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) { + // snip is the whole *help* message that appears after the warning. + // message is the warning message. + // expr is the expression which the lint warning message refers to. + let (snip, message, expr) = match typ { + LintType::ContinueInsideElseBlock => ( + suggestion_snippet_for_continue_inside_else(cx, data), + MSG_REDUNDANT_ELSE_BLOCK, + data.else_expr, + ), + LintType::ContinueInsideThenBlock => ( + suggestion_snippet_for_continue_inside_if(cx, data), + MSG_ELSE_BLOCK_NOT_NEEDED, + data.if_expr, + ), + }; + span_lint_and_help( + cx, + NEEDLESS_CONTINUE, + expr.span, + message, + None, + &format!("{}\n{}", header, snip), + ); +} + +fn suggestion_snippet_for_continue_inside_if<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String { + let cond_code = snippet(cx, data.if_cond.span, ".."); + + let continue_code = snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)); + + let else_code = snippet_block(cx, data.else_expr.span, "..", Some(data.if_expr.span)); + + let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0); + format!( + "{indent}if {} {}\n{indent}{}", + cond_code, + continue_code, + else_code, + indent = " ".repeat(indent_if), + ) +} + +fn suggestion_snippet_for_continue_inside_else<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String { + let cond_code = snippet(cx, data.if_cond.span, ".."); + + // Region B + let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span))); + + // Region C + // These is the code in the loop block that follows the if/else construction + // we are complaining about. We want to pull all of this code into the + // `then` block of the `if` statement. + let indent = span_of_first_expr_in_block(data.if_block) + .and_then(|span| indent_of(cx, span)) + .unwrap_or(0); + let to_annex = data.block_stmts[data.stmt_idx + 1..] + .iter() + .map(|stmt| original_sp(stmt.span, DUMMY_SP)) + .map(|span| { + let snip = snippet_block(cx, span, "..", None).into_owned(); + snip.lines() + .map(|line| format!("{}{}", " ".repeat(indent), line)) + .collect::>() + .join("\n") + }) + .collect::>() + .join("\n"); + + let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0); + format!( + "{indent_if}if {} {}\n{indent}// merged code follows:\n{}\n{indent_if}}}", + cond_code, + block_code, + to_annex, + indent = " ".repeat(indent), + indent_if = " ".repeat(indent_if), + ) +} + +fn check_and_warn<'a>(cx: &EarlyContext<'_>, expr: &'a ast::Expr) { + with_loop_block(expr, |loop_block, label| { + for (i, stmt) in loop_block.stmts.iter().enumerate() { + with_if_expr(stmt, |if_expr, cond, then_block, else_expr| { + let data = &LintData { + stmt_idx: i, + if_expr, + if_cond: cond, + if_block: then_block, + else_expr, + block_stmts: &loop_block.stmts, + }; + if needless_continue_in_else(else_expr, label) { + emit_warning( + cx, + data, + DROP_ELSE_BLOCK_AND_MERGE_MSG, + LintType::ContinueInsideElseBlock, + ); + } else if is_first_block_stmt_continue(then_block, label) { + emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock); + } + }); + } + }); +} + +/// Eats at `s` from the end till a closing brace `}` is encountered, and then continues eating +/// till a non-whitespace character is found. e.g., the string. If no closing `}` is present, the +/// string will be preserved. +/// +/// ```rust +/// { +/// let x = 5; +/// } +/// ``` +/// +/// is transformed to +/// +/// ```ignore +/// { +/// let x = 5; +/// ``` +#[must_use] +fn erode_from_back(s: &str) -> String { + let mut ret = s.to_string(); + while ret.pop().map_or(false, |c| c != '}') {} + while let Some(c) = ret.pop() { + if !c.is_whitespace() { + ret.push(c); + break; + } + } + if ret.is_empty() { + s.to_string() + } else { + ret + } +} + +fn span_of_first_expr_in_block(block: &ast::Block) -> Option { + block.stmts.iter().next().map(|stmt| stmt.span) +} + +#[cfg(test)] +mod test { + use super::erode_from_back; + + #[test] + #[rustfmt::skip] + fn test_erode_from_back() { + let input = "\ +{ + let x = 5; + let y = format!(\"{}\", 42); +}"; + + let expected = "\ +{ + let x = 5; + let y = format!(\"{}\", 42);"; + + let got = erode_from_back(input); + assert_eq!(expected, got); + } + + #[test] + #[rustfmt::skip] + fn test_erode_from_back_no_brace() { + let input = "\ +let x = 5; +let y = something(); +"; + let expected = input; + let got = erode_from_back(input); + assert_eq!(expected, got); + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs new file mode 100644 index 0000000000000..a21818701dacc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -0,0 +1,347 @@ +use crate::utils::ptr::get_spans; +use crate::utils::{ + get_trait_def_id, implements_trait, is_copy, is_self, is_type_diagnostic_item, multispan_sugg, paths, snippet, + snippet_opt, span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_ast::ast::Attribute; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, ItemKind, Node, PatKind, QPath, TyKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, TypeFoldable}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits; +use rustc_trait_selection::traits::misc::can_type_implement_copy; +use rustc_typeck::expr_use_visitor as euv; +use std::borrow::Cow; + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by value, but not + /// consuming them in its + /// body. + /// + /// **Why is this bad?** Taking arguments by reference is more flexible and can + /// sometimes avoid + /// unnecessary allocations. + /// + /// **Known problems:** + /// * This lint suggests taking an argument by reference, + /// however sometimes it is better to let users decide the argument type + /// (by using `Borrow` trait, for example), depending on how the function is used. + /// + /// **Example:** + /// ```rust + /// fn foo(v: Vec) { + /// assert_eq!(v.len(), 42); + /// } + /// ``` + /// + /// ```rust + /// // should be + /// fn foo(v: &[i32]) { + /// assert_eq!(v.len(), 42); + /// } + /// ``` + pub NEEDLESS_PASS_BY_VALUE, + pedantic, + "functions taking arguments by value, but not consuming them in its body" +} + +declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]); + +macro_rules! need { + ($e: expr) => { + if let Some(x) = $e { + x + } else { + return; + } + }; +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { + #[allow(clippy::too_many_lines)] + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if span.from_expansion() { + return; + } + + match kind { + FnKind::ItemFn(.., header, _, attrs) => { + if header.abi != Abi::Rust || requires_exact_signature(attrs) { + return; + } + }, + FnKind::Method(..) => (), + _ => return, + } + + // Exclude non-inherent impls + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + if matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. } | + ItemKind::Trait(..)) + { + return; + } + } + + // Allow `Borrow` or functions to be taken by value + let borrow_trait = need!(get_trait_def_id(cx, &paths::BORROW_TRAIT)); + let whitelisted_traits = [ + need!(cx.tcx.lang_items().fn_trait()), + need!(cx.tcx.lang_items().fn_once_trait()), + need!(cx.tcx.lang_items().fn_mut_trait()), + need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)), + ]; + + let sized_trait = need!(cx.tcx.lang_items().sized_trait()); + + let fn_def_id = cx.tcx.hir().local_def_id(hir_id); + + let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.iter().copied()) + .filter(|p| !p.is_global()) + .filter_map(|obligation| { + if let ty::Predicate::Trait(poly_trait_ref, _) = obligation.predicate { + if poly_trait_ref.def_id() == sized_trait || poly_trait_ref.skip_binder().has_escaping_bound_vars() + { + return None; + } + Some(poly_trait_ref) + } else { + None + } + }) + .collect::>(); + + // Collect moved variables and spans which will need dereferencings from the + // function body. + let MovedVariablesCtxt { + moved_vars, + spans_need_deref, + .. + } = { + let mut ctx = MovedVariablesCtxt::default(); + cx.tcx.infer_ctxt().enter(|infcx| { + euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.tables).consume_body(body); + }); + ctx + }; + + let fn_sig = cx.tcx.fn_sig(fn_def_id); + let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig); + + for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() { + // All spans generated from a proc-macro invocation are the same... + if span == input.span { + return; + } + + // Ignore `self`s. + if idx == 0 { + if let PatKind::Binding(.., ident, _) = arg.pat.kind { + if ident.as_str() == "self" { + continue; + } + } + } + + // + // * Exclude a type that is specifically bounded by `Borrow`. + // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`, + // `serde::Serialize`) + let (implements_borrow_trait, all_borrowable_trait) = { + let preds = preds + .iter() + .filter(|t| t.skip_binder().self_ty() == ty) + .collect::>(); + + ( + preds.iter().any(|t| t.def_id() == borrow_trait), + !preds.is_empty() && { + let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty); + preds.iter().all(|t| { + let ty_params = &t + .skip_binder() + .trait_ref + .substs + .iter() + .skip(1) + .cloned() + .collect::>(); + implements_trait(cx, ty_empty_region, t.def_id(), ty_params) + }) + }, + ) + }; + + if_chain! { + if !is_self(arg); + if !ty.is_mutable_ptr(); + if !is_copy(cx, ty); + if !whitelisted_traits.iter().any(|&t| implements_trait(cx, ty, t, &[])); + if !implements_borrow_trait; + if !all_borrowable_trait; + + if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.kind; + if !moved_vars.contains(&canonical_id); + then { + if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut { + continue; + } + + // Dereference suggestion + let sugg = |diag: &mut DiagnosticBuilder<'_>| { + if let ty::Adt(def, ..) = ty.kind { + if let Some(span) = cx.tcx.hir().span_if_local(def.did) { + if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() { + diag.span_help(span, "consider marking this type as `Copy`"); + } + } + } + + let deref_span = spans_need_deref.get(&canonical_id); + if_chain! { + if is_type_diagnostic_item(cx, ty, sym!(vec_type)); + if let Some(clone_spans) = + get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]); + if let TyKind::Path(QPath::Resolved(_, ref path)) = input.kind; + if let Some(elem_ty) = path.segments.iter() + .find(|seg| seg.ident.name == sym!(Vec)) + .and_then(|ps| ps.args.as_ref()) + .map(|params| params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).unwrap()); + then { + let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_")); + diag.span_suggestion( + input.span, + "consider changing the type to", + slice_ty, + Applicability::Unspecified, + ); + + for (span, suggestion) in clone_spans { + diag.span_suggestion( + span, + &snippet_opt(cx, span) + .map_or( + "change the call to".into(), + |x| Cow::from(format!("change `{}` to", x)), + ), + suggestion.into(), + Applicability::Unspecified, + ); + } + + // cannot be destructured, no need for `*` suggestion + assert!(deref_span.is_none()); + return; + } + } + + if is_type_diagnostic_item(cx, ty, sym!(string_type)) { + if let Some(clone_spans) = + get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) { + diag.span_suggestion( + input.span, + "consider changing the type to", + "&str".to_string(), + Applicability::Unspecified, + ); + + for (span, suggestion) in clone_spans { + diag.span_suggestion( + span, + &snippet_opt(cx, span) + .map_or( + "change the call to".into(), + |x| Cow::from(format!("change `{}` to", x)) + ), + suggestion.into(), + Applicability::Unspecified, + ); + } + + assert!(deref_span.is_none()); + return; + } + } + + let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))]; + + // Suggests adding `*` to dereference the added reference. + if let Some(deref_span) = deref_span { + spans.extend( + deref_span + .iter() + .cloned() + .map(|span| (span, format!("*{}", snippet(cx, span, "")))), + ); + spans.sort_by_key(|&(span, _)| span); + } + multispan_sugg(diag, "consider taking a reference instead".to_string(), spans); + }; + + span_lint_and_then( + cx, + NEEDLESS_PASS_BY_VALUE, + input.span, + "this argument is passed by value, but not consumed in the function body", + sugg, + ); + } + } + } + } +} + +/// Functions marked with these attributes must have the exact signature. +fn requires_exact_signature(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| { + [sym!(proc_macro), sym!(proc_macro_attribute), sym!(proc_macro_derive)] + .iter() + .any(|&allow| attr.check_name(allow)) + }) +} + +#[derive(Default)] +struct MovedVariablesCtxt { + moved_vars: FxHashSet, + /// Spans which need to be prefixed with `*` for dereferencing the + /// suggested additional reference. + spans_need_deref: FxHashMap>, +} + +impl MovedVariablesCtxt { + fn move_common(&mut self, cmt: &euv::Place<'_>) { + if let euv::PlaceBase::Local(vid) = cmt.base { + self.moved_vars.insert(vid); + } + } +} + +impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt { + fn consume(&mut self, cmt: &euv::Place<'tcx>, mode: euv::ConsumeMode) { + if let euv::ConsumeMode::Move = mode { + self.move_common(cmt); + } + } + + fn borrow(&mut self, _: &euv::Place<'tcx>, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &euv::Place<'tcx>) {} +} diff --git a/src/tools/clippy/clippy_lints/src/needless_update.rs b/src/tools/clippy/clippy_lints/src/needless_update.rs new file mode 100644 index 0000000000000..4b2586877e562 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/needless_update.rs @@ -0,0 +1,53 @@ +use crate::utils::span_lint; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for needlessly including a base struct on update + /// when all fields are changed anyway. + /// + /// **Why is this bad?** This will cost resources (because the base has to be + /// somewhere), and make the code less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct Point { + /// # x: i32, + /// # y: i32, + /// # z: i32, + /// # } + /// # let zero_point = Point { x: 0, y: 0, z: 0 }; + /// Point { + /// x: 1, + /// y: 1, + /// ..zero_point + /// }; + /// ``` + pub NEEDLESS_UPDATE, + complexity, + "using `Foo { ..base }` when there are no missing fields" +} + +declare_lint_pass!(NeedlessUpdate => [NEEDLESS_UPDATE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessUpdate { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Struct(_, ref fields, Some(ref base)) = expr.kind { + let ty = cx.tables.expr_ty(expr); + if let ty::Adt(def, _) = ty.kind { + if fields.len() == def.non_enum_variant().fields.len() { + span_lint( + cx, + NEEDLESS_UPDATE, + base.span, + "struct update has no effect, all the fields in the struct have already been specified", + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/neg_cmp_op_on_partial_ord.rs b/src/tools/clippy/clippy_lints/src/neg_cmp_op_on_partial_ord.rs new file mode 100644 index 0000000000000..54536ed57d3e9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/neg_cmp_op_on_partial_ord.rs @@ -0,0 +1,91 @@ +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{self, paths, span_lint}; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for the usage of negated comparison operators on types which only implement + /// `PartialOrd` (e.g., `f64`). + /// + /// **Why is this bad?** + /// These operators make it easy to forget that the underlying types actually allow not only three + /// potential Orderings (Less, Equal, Greater) but also a fourth one (Uncomparable). This is + /// especially easy to miss if the operator based comparison result is negated. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::cmp::Ordering; + /// + /// // Bad + /// let a = 1.0; + /// let b = f64::NAN; + /// + /// let _not_less_or_equal = !(a <= b); + /// + /// // Good + /// let a = 1.0; + /// let b = f64::NAN; + /// + /// let _not_less_or_equal = match a.partial_cmp(&b) { + /// None | Some(Ordering::Greater) => true, + /// _ => false, + /// }; + /// ``` + pub NEG_CMP_OP_ON_PARTIAL_ORD, + complexity, + "The use of negated comparison operators on partially ordered types may produce confusing code." +} + +declare_lint_pass!(NoNegCompOpForPartialOrd => [NEG_CMP_OP_ON_PARTIAL_ORD]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NoNegCompOpForPartialOrd { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + + if !in_external_macro(cx.sess(), expr.span); + if let ExprKind::Unary(UnOp::UnNot, ref inner) = expr.kind; + if let ExprKind::Binary(ref op, ref left, _) = inner.kind; + if let BinOpKind::Le | BinOpKind::Ge | BinOpKind::Lt | BinOpKind::Gt = op.node; + + then { + + let ty = cx.tables.expr_ty(left); + + let implements_ord = { + if let Some(id) = utils::get_trait_def_id(cx, &paths::ORD) { + utils::implements_trait(cx, ty, id, &[]) + } else { + return; + } + }; + + let implements_partial_ord = { + if let Some(id) = cx.tcx.lang_items().partial_ord_trait() { + utils::implements_trait(cx, ty, id, &[]) + } else { + return; + } + }; + + if implements_partial_ord && !implements_ord { + span_lint( + cx, + NEG_CMP_OP_ON_PARTIAL_ORD, + expr.span, + "The use of negated comparison operators on partially ordered \ + types produces code that is hard to read and refactor. Please \ + consider using the `partial_cmp` method instead, to make it \ + clear that the two values could be incomparable." + ) + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs new file mode 100644 index 0000000000000..4681e990df88a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs @@ -0,0 +1,53 @@ +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +use crate::consts::{self, Constant}; +use crate::utils::span_lint; + +declare_clippy_lint! { + /// **What it does:** Checks for multiplication by -1 as a form of negation. + /// + /// **Why is this bad?** It's more readable to just negate. + /// + /// **Known problems:** This only catches integers (for now). + /// + /// **Example:** + /// ```ignore + /// x * -1 + /// ``` + pub NEG_MULTIPLY, + style, + "multiplying integers with `-1`" +} + +declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]); + +#[allow(clippy::match_same_arms)] +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NegMultiply { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref op, ref left, ref right) = e.kind { + if BinOpKind::Mul == op.node { + match (&left.kind, &right.kind) { + (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {}, + (&ExprKind::Unary(UnOp::UnNeg, ref lit), _) => check_mul(cx, e.span, lit, right), + (_, &ExprKind::Unary(UnOp::UnNeg, ref lit)) => check_mul(cx, e.span, lit, left), + _ => {}, + } + } + } + } +} + +fn check_mul(cx: &LateContext<'_, '_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { + if_chain! { + if let ExprKind::Lit(ref l) = lit.kind; + if let Constant::Int(1) = consts::lit_to_constant(&l.node, cx.tables.expr_ty_opt(lit)); + if cx.tables.expr_ty(exp).is_integral(); + then { + span_lint(cx, NEG_MULTIPLY, span, "Negation by multiplying with `-1`"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs new file mode 100644 index 0000000000000..a599667b8d8a8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/new_without_default.rs @@ -0,0 +1,234 @@ +use crate::utils::paths; +use crate::utils::sugg::DiagnosticBuilderExt; +use crate::utils::{get_trait_def_id, implements_trait, return_ty, same_tys, span_lint_hir_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_hir::HirIdSet; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for types with a `fn new() -> Self` method and no + /// implementation of + /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html). + /// + /// It detects both the case when a manual + /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) + /// implementation is required and also when it can be created with + /// `#[derive(Default)]` + /// + /// **Why is this bad?** The user might expect to be able to use + /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the + /// type can be constructed without arguments. + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// + /// ```ignore + /// struct Foo(Bar); + /// + /// impl Foo { + /// fn new() -> Self { + /// Foo(Bar::new()) + /// } + /// } + /// ``` + /// + /// Instead, use: + /// + /// ```ignore + /// struct Foo(Bar); + /// + /// impl Default for Foo { + /// fn default() -> Self { + /// Foo(Bar::new()) + /// } + /// } + /// ``` + /// + /// Or, if + /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) + /// can be derived by `#[derive(Default)]`: + /// + /// ```rust + /// struct Foo; + /// + /// impl Foo { + /// fn new() -> Self { + /// Foo + /// } + /// } + /// ``` + /// + /// Instead, use: + /// + /// ```rust + /// #[derive(Default)] + /// struct Foo; + /// + /// impl Foo { + /// fn new() -> Self { + /// Foo + /// } + /// } + /// ``` + /// + /// You can also have `new()` call `Default::default()`. + pub NEW_WITHOUT_DEFAULT, + style, + "`fn new() -> Self` method without `Default` implementation" +} + +#[derive(Clone, Default)] +pub struct NewWithoutDefault { + impling_types: Option, +} + +impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NewWithoutDefault { + #[allow(clippy::too_many_lines)] + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + if let hir::ItemKind::Impl { + of_trait: None, items, .. + } = item.kind + { + for assoc_item in items { + if let hir::AssocItemKind::Fn { has_self: false } = assoc_item.kind { + let impl_item = cx.tcx.hir().impl_item(assoc_item.id); + if in_external_macro(cx.sess(), impl_item.span) { + return; + } + if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { + let name = impl_item.ident.name; + let id = impl_item.hir_id; + if sig.header.constness == hir::Constness::Const { + // can't be implemented by default + return; + } + if sig.header.unsafety == hir::Unsafety::Unsafe { + // can't be implemented for unsafe new + return; + } + if impl_item.generics.params.iter().any(|gen| match gen.kind { + hir::GenericParamKind::Type { .. } => true, + _ => false, + }) { + // when the result of `new()` depends on a type parameter we should not require + // an + // impl of `Default` + return; + } + if sig.decl.inputs.is_empty() && name == sym!(new) && cx.access_levels.is_reachable(id) { + let self_did = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); + let self_ty = cx.tcx.type_of(self_did); + if_chain! { + if same_tys(cx, self_ty, return_ty(cx, id)); + if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); + then { + if self.impling_types.is_none() { + let mut impls = HirIdSet::default(); + cx.tcx.for_each_impl(default_trait_id, |d| { + if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { + if let Some(local_def_id) = ty_def.did.as_local() { + impls.insert(cx.tcx.hir().as_local_hir_id(local_def_id)); + } + } + }); + self.impling_types = Some(impls); + } + + // Check if a Default implementation exists for the Self type, regardless of + // generics + if_chain! { + if let Some(ref impling_types) = self.impling_types; + if let Some(self_def) = cx.tcx.type_of(self_did).ty_adt_def(); + if let Some(self_def_id) = self_def.did.as_local(); + then { + let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_def_id); + if impling_types.contains(&self_id) { + return; + } + } + } + + if let Some(sp) = can_derive_default(self_ty, cx, default_trait_id) { + span_lint_hir_and_then( + cx, + NEW_WITHOUT_DEFAULT, + id, + impl_item.span, + &format!( + "you should consider deriving a `Default` implementation for `{}`", + self_ty + ), + |diag| { + diag.suggest_item_with_attr( + cx, + sp, + "try this", + "#[derive(Default)]", + Applicability::MaybeIncorrect, + ); + }); + } else { + span_lint_hir_and_then( + cx, + NEW_WITHOUT_DEFAULT, + id, + impl_item.span, + &format!( + "you should consider adding a `Default` implementation for `{}`", + self_ty + ), + |diag| { + diag.suggest_prepend_item( + cx, + item.span, + "try this", + &create_new_without_default_suggest_msg(self_ty), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + } + } + } + } + } + } + } +} + +fn create_new_without_default_suggest_msg(ty: Ty<'_>) -> String { + #[rustfmt::skip] + format!( +"impl Default for {} {{ + fn default() -> Self {{ + Self::new() + }} +}}", ty) +} + +fn can_derive_default<'t, 'c>(ty: Ty<'t>, cx: &LateContext<'c, 't>, default_trait_id: DefId) -> Option { + match ty.kind { + ty::Adt(adt_def, substs) if adt_def.is_struct() => { + for field in adt_def.all_fields() { + let f_ty = field.ty(cx.tcx, substs); + if !implements_trait(cx, f_ty, default_trait_id, &[]) { + return None; + } + } + Some(cx.tcx.def_span(adt_def.did)) + }, + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs new file mode 100644 index 0000000000000..b8bfa676a1608 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/no_effect.rs @@ -0,0 +1,177 @@ +use crate::utils::{has_drop, qpath_res, snippet_opt, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOpKind, BlockCheckMode, Expr, ExprKind, Stmt, StmtKind, UnsafeSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::ops::Deref; + +declare_clippy_lint! { + /// **What it does:** Checks for statements which have no effect. + /// + /// **Why is this bad?** Similar to dead code, these statements are actually + /// executed. However, as they have no effect, all they do is make the code less + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// 0; + /// ``` + pub NO_EFFECT, + complexity, + "statements with no effect" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expression statements that can be reduced to a + /// sub-expression. + /// + /// **Why is this bad?** Expressions by themselves often have no side-effects. + /// Having such expressions reduces readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// compute_array()[0]; + /// ``` + pub UNNECESSARY_OPERATION, + complexity, + "outer expressions with no effect" +} + +fn has_no_effect(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + if expr.span.from_expansion() { + return false; + } + match expr.kind { + ExprKind::Lit(..) | ExprKind::Closure(..) => true, + ExprKind::Path(..) => !has_drop(cx, cx.tables.expr_ty(expr)), + ExprKind::Index(ref a, ref b) | ExprKind::Binary(_, ref a, ref b) => { + has_no_effect(cx, a) && has_no_effect(cx, b) + }, + ExprKind::Array(ref v) | ExprKind::Tup(ref v) => v.iter().all(|val| has_no_effect(cx, val)), + ExprKind::Repeat(ref inner, _) + | ExprKind::Cast(ref inner, _) + | ExprKind::Type(ref inner, _) + | ExprKind::Unary(_, ref inner) + | ExprKind::Field(ref inner, _) + | ExprKind::AddrOf(_, _, ref inner) + | ExprKind::Box(ref inner) => has_no_effect(cx, inner), + ExprKind::Struct(_, ref fields, ref base) => { + !has_drop(cx, cx.tables.expr_ty(expr)) + && fields.iter().all(|field| has_no_effect(cx, &field.expr)) + && base.as_ref().map_or(true, |base| has_no_effect(cx, base)) + }, + ExprKind::Call(ref callee, ref args) => { + if let ExprKind::Path(ref qpath) = callee.kind { + let res = qpath_res(cx, qpath, callee.hir_id); + match res { + Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..) => { + !has_drop(cx, cx.tables.expr_ty(expr)) && args.iter().all(|arg| has_no_effect(cx, arg)) + }, + _ => false, + } + } else { + false + } + }, + ExprKind::Block(ref block, _) => { + block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| has_no_effect(cx, expr)) + }, + _ => false, + } +} + +declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NoEffect { + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Semi(ref expr) = stmt.kind { + if has_no_effect(cx, expr) { + span_lint(cx, NO_EFFECT, stmt.span, "statement with no effect"); + } else if let Some(reduced) = reduce_expression(cx, expr) { + let mut snippet = String::new(); + for e in reduced { + if e.span.from_expansion() { + return; + } + if let Some(snip) = snippet_opt(cx, e.span) { + snippet.push_str(&snip); + snippet.push(';'); + } else { + return; + } + } + span_lint_and_sugg( + cx, + UNNECESSARY_OPERATION, + stmt.span, + "statement can be reduced", + "replace it with", + snippet, + Applicability::MachineApplicable, + ); + } + } + } +} + +fn reduce_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'a>) -> Option>> { + if expr.span.from_expansion() { + return None; + } + match expr.kind { + ExprKind::Index(ref a, ref b) => Some(vec![&**a, &**b]), + ExprKind::Binary(ref binop, ref a, ref b) if binop.node != BinOpKind::And && binop.node != BinOpKind::Or => { + Some(vec![&**a, &**b]) + }, + ExprKind::Array(ref v) | ExprKind::Tup(ref v) => Some(v.iter().collect()), + ExprKind::Repeat(ref inner, _) + | ExprKind::Cast(ref inner, _) + | ExprKind::Type(ref inner, _) + | ExprKind::Unary(_, ref inner) + | ExprKind::Field(ref inner, _) + | ExprKind::AddrOf(_, _, ref inner) + | ExprKind::Box(ref inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])), + ExprKind::Struct(_, ref fields, ref base) => { + if has_drop(cx, cx.tables.expr_ty(expr)) { + None + } else { + Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect()) + } + }, + ExprKind::Call(ref callee, ref args) => { + if let ExprKind::Path(ref qpath) = callee.kind { + let res = qpath_res(cx, qpath, callee.hir_id); + match res { + Res::Def(DefKind::Struct, ..) | Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(..), _) + if !has_drop(cx, cx.tables.expr_ty(expr)) => + { + Some(args.iter().collect()) + }, + _ => None, + } + } else { + None + } + }, + ExprKind::Block(ref block, _) => { + if block.stmts.is_empty() { + block.expr.as_ref().and_then(|e| { + match block.rules { + BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None, + BlockCheckMode::DefaultBlock => Some(vec![&**e]), + // in case of compiler-inserted signaling blocks + _ => reduce_expression(cx, e), + } + }) + } else { + None + } + }, + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs new file mode 100644 index 0000000000000..bb257e5a542d9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -0,0 +1,261 @@ +//! Checks for uses of const which the type is not `Freeze` (`Cell`-free). +//! +//! This lint is **deny** by default. + +use std::ptr; + +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_middle::ty::adjustment::Adjust; +use rustc_middle::ty::{Ty, TypeFlags}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{InnerSpan, Span, DUMMY_SP}; +use rustc_typeck::hir_ty_to_ty; + +use crate::utils::{in_constant, is_copy, qpath_res, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for declaration of `const` items which is interior + /// mutable (e.g., contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.). + /// + /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e., + /// every time you refer to the const a fresh instance of the `Cell` or `Mutex` + /// or `AtomicXxxx` will be created, which defeats the whole purpose of using + /// these types in the first place. + /// + /// The `const` should better be replaced by a `static` item if a global + /// variable is wanted, or replaced by a `const fn` if a constructor is wanted. + /// + /// **Known problems:** A "non-constant" const item is a legacy way to supply an + /// initialized value to downstream `static` items (e.g., the + /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit, + /// and this lint should be suppressed. + /// + /// **Example:** + /// ```rust + /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + /// + /// // Bad. + /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); + /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged + /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// + /// // Good. + /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15); + /// STATIC_ATOM.store(9, SeqCst); + /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance + /// ``` + pub DECLARE_INTERIOR_MUTABLE_CONST, + correctness, + "declaring `const` with interior mutability" +} + +declare_clippy_lint! { + /// **What it does:** Checks if `const` items which is interior mutable (e.g., + /// contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly. + /// + /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e., + /// every time you refer to the const a fresh instance of the `Cell` or `Mutex` + /// or `AtomicXxxx` will be created, which defeats the whole purpose of using + /// these types in the first place. + /// + /// The `const` value should be stored inside a `static` item. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); + /// + /// // Bad. + /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged + /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// + /// // Good. + /// static STATIC_ATOM: AtomicUsize = CONST_ATOM; + /// STATIC_ATOM.store(9, SeqCst); + /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance + /// ``` + pub BORROW_INTERIOR_MUTABLE_CONST, + correctness, + "referencing `const` with interior mutability" +} + +#[allow(dead_code)] +#[derive(Copy, Clone)] +enum Source { + Item { item: Span }, + Assoc { item: Span, ty: Span }, + Expr { expr: Span }, +} + +impl Source { + #[must_use] + fn lint(&self) -> (&'static Lint, &'static str, Span) { + match self { + Self::Item { item } | Self::Assoc { item, .. } => ( + DECLARE_INTERIOR_MUTABLE_CONST, + "a `const` item should never be interior mutable", + *item, + ), + Self::Expr { expr } => ( + BORROW_INTERIOR_MUTABLE_CONST, + "a `const` item with interior mutability should not be borrowed", + *expr, + ), + } + } +} + +fn verify_ty_bound<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>, source: Source) { + if ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP) || is_copy(cx, ty) { + // An `UnsafeCell` is `!Copy`, and an `UnsafeCell` is also the only type which + // is `!Freeze`, thus if our type is `Copy` we can be sure it must be `Freeze` + // as well. + return; + } + + let (lint, msg, span) = source.lint(); + span_lint_and_then(cx, lint, span, msg, |diag| { + if span.from_expansion() { + return; // Don't give suggestions into macros. + } + match source { + Source::Item { .. } => { + let const_kw_span = span.from_inner(InnerSpan::new(0, 5)); + diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)"); + }, + Source::Assoc { ty: ty_span, .. } => { + if ty.flags.intersects(TypeFlags::HAS_FREE_LOCAL_NAMES) { + diag.span_label(ty_span, &format!("consider requiring `{}` to be `Copy`", ty)); + } + }, + Source::Expr { .. } => { + diag.help("assign this const to a local or static variable, and use the variable here"); + }, + } + }); +} + +declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonCopyConst { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, it: &'tcx Item<'_>) { + if let ItemKind::Const(hir_ty, ..) = &it.kind { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + verify_ty_bound(cx, ty, Source::Item { item: it.span }); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, trait_item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Const(hir_ty, ..) = &trait_item.kind { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + verify_ty_bound( + cx, + ty, + Source::Assoc { + ty: hir_ty.span, + item: trait_item.span, + }, + ); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Const(hir_ty, ..) = &impl_item.kind { + let item_hir_id = cx.tcx.hir().get_parent_node(impl_item.hir_id); + let item = cx.tcx.hir().expect_item(item_hir_id); + // Ensure the impl is an inherent impl. + if let ItemKind::Impl { of_trait: None, .. } = item.kind { + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + verify_ty_bound( + cx, + ty, + Source::Assoc { + ty: hir_ty.span, + item: impl_item.span, + }, + ); + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Path(qpath) = &expr.kind { + // Only lint if we use the const item inside a function. + if in_constant(cx, expr.hir_id) { + return; + } + + // Make sure it is a const item. + match qpath_res(cx, qpath, expr.hir_id) { + Res::Def(DefKind::Const | DefKind::AssocConst, _) => {}, + _ => return, + }; + + // Climb up to resolve any field access and explicit referencing. + let mut cur_expr = expr; + let mut dereferenced_expr = expr; + let mut needs_check_adjustment = true; + loop { + let parent_id = cx.tcx.hir().get_parent_node(cur_expr.hir_id); + if parent_id == cur_expr.hir_id { + break; + } + if let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find(parent_id) { + match &parent_expr.kind { + ExprKind::AddrOf(..) => { + // `&e` => `e` must be referenced. + needs_check_adjustment = false; + }, + ExprKind::Field(..) => { + dereferenced_expr = parent_expr; + needs_check_adjustment = true; + }, + ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => { + // `e[i]` => desugared to `*Index::index(&e, i)`, + // meaning `e` must be referenced. + // no need to go further up since a method call is involved now. + needs_check_adjustment = false; + break; + }, + ExprKind::Unary(UnOp::UnDeref, _) => { + // `*e` => desugared to `*Deref::deref(&e)`, + // meaning `e` must be referenced. + // no need to go further up since a method call is involved now. + needs_check_adjustment = false; + break; + }, + _ => break, + } + cur_expr = parent_expr; + } else { + break; + } + } + + let ty = if needs_check_adjustment { + let adjustments = cx.tables.expr_adjustments(dereferenced_expr); + if let Some(i) = adjustments.iter().position(|adj| match adj.kind { + Adjust::Borrow(_) | Adjust::Deref(_) => true, + _ => false, + }) { + if i == 0 { + cx.tables.expr_ty(dereferenced_expr) + } else { + adjustments[i - 1].target + } + } else { + // No borrow adjustments means the entire const is moved. + return; + } + } else { + cx.tables.expr_ty(dereferenced_expr) + }; + + verify_ty_bound(cx, ty, Source::Expr { expr: expr.span }); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs new file mode 100644 index 0000000000000..45809b3598661 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs @@ -0,0 +1,410 @@ +use crate::utils::{span_lint, span_lint_and_then}; +use rustc_ast::ast::{ + Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, Ident, Item, ItemKind, Local, MacCall, Pat, PatKind, +}; +use rustc_ast::attr; +use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::Span; +use rustc_span::symbol::SymbolStr; +use std::cmp::Ordering; + +declare_clippy_lint! { + /// **What it does:** Checks for names that are very similar and thus confusing. + /// + /// **Why is this bad?** It's hard to distinguish between names that differ only + /// by a single character. + /// + /// **Known problems:** None? + /// + /// **Example:** + /// ```ignore + /// let checked_exp = something; + /// let checked_expr = something_else; + /// ``` + pub SIMILAR_NAMES, + pedantic, + "similarly named items and bindings" +} + +declare_clippy_lint! { + /// **What it does:** Checks for too many variables whose name consists of a + /// single character. + /// + /// **Why is this bad?** It's hard to memorize what a variable means without a + /// descriptive name. + /// + /// **Known problems:** None? + /// + /// **Example:** + /// ```ignore + /// let (a, b, c, d, e, f, g) = (...); + /// ``` + pub MANY_SINGLE_CHAR_NAMES, + style, + "too many single character bindings" +} + +declare_clippy_lint! { + /// **What it does:** Checks if you have variables whose name consists of just + /// underscores and digits. + /// + /// **Why is this bad?** It's hard to memorize what a variable means without a + /// descriptive name. + /// + /// **Known problems:** None? + /// + /// **Example:** + /// ```rust + /// let _1 = 1; + /// let ___1 = 1; + /// let __1___2 = 11; + /// ``` + pub JUST_UNDERSCORES_AND_DIGITS, + style, + "unclear name" +} + +#[derive(Copy, Clone)] +pub struct NonExpressiveNames { + pub single_char_binding_names_threshold: u64, +} + +impl_lint_pass!(NonExpressiveNames => [SIMILAR_NAMES, MANY_SINGLE_CHAR_NAMES, JUST_UNDERSCORES_AND_DIGITS]); + +struct ExistingName { + interned: SymbolStr, + span: Span, + len: usize, + whitelist: &'static [&'static str], +} + +struct SimilarNamesLocalVisitor<'a, 'tcx> { + names: Vec, + cx: &'a EarlyContext<'tcx>, + lint: &'a NonExpressiveNames, + + /// A stack of scopes containing the single-character bindings in each scope. + single_char_names: Vec>, +} + +impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> { + fn check_single_char_names(&self) { + let num_single_char_names = self.single_char_names.iter().flatten().count(); + let threshold = self.lint.single_char_binding_names_threshold; + if num_single_char_names as u64 > threshold { + let span = self + .single_char_names + .iter() + .flatten() + .map(|ident| ident.span) + .collect::>(); + span_lint( + self.cx, + MANY_SINGLE_CHAR_NAMES, + span, + &format!( + "{} bindings with single-character names in scope", + num_single_char_names + ), + ); + } + } +} + +// this list contains lists of names that are allowed to be similar +// the assumption is that no name is ever contained in multiple lists. +#[rustfmt::skip] +const WHITELIST: &[&[&str]] = &[ + &["parsed", "parser"], + &["lhs", "rhs"], + &["tx", "rx"], + &["set", "get"], + &["args", "arms"], + &["qpath", "path"], + &["lit", "lint"], +]; + +struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>); + +impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { + fn visit_pat(&mut self, pat: &'tcx Pat) { + match pat.kind { + PatKind::Ident(_, ident, _) => self.check_ident(ident), + PatKind::Struct(_, ref fields, _) => { + for field in fields { + if !field.is_shorthand { + self.visit_pat(&field.pat); + } + } + }, + // just go through the first pattern, as either all patterns + // bind the same bindings or rustc would have errored much earlier + PatKind::Or(ref pats) => self.visit_pat(&pats[0]), + _ => walk_pat(self, pat), + } + } + fn visit_mac(&mut self, _mac: &MacCall) { + // do not check macs + } +} + +#[must_use] +fn get_whitelist(interned_name: &str) -> Option<&'static [&'static str]> { + for &allow in WHITELIST { + if whitelisted(interned_name, allow) { + return Some(allow); + } + } + None +} + +#[must_use] +fn whitelisted(interned_name: &str, list: &[&str]) -> bool { + list.iter() + .any(|&name| interned_name.starts_with(name) || interned_name.ends_with(name)) +} + +impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> { + fn check_short_ident(&mut self, ident: Ident) { + // Ignore shadowing + if self + .0 + .single_char_names + .iter() + .flatten() + .any(|id| id.name == ident.name) + { + return; + } + + if let Some(scope) = &mut self.0.single_char_names.last_mut() { + scope.push(ident); + } + } + + #[allow(clippy::too_many_lines)] + fn check_ident(&mut self, ident: Ident) { + let interned_name = ident.name.as_str(); + if interned_name.chars().any(char::is_uppercase) { + return; + } + if interned_name.chars().all(|c| c.is_digit(10) || c == '_') { + span_lint( + self.0.cx, + JUST_UNDERSCORES_AND_DIGITS, + ident.span, + "consider choosing a more descriptive name", + ); + return; + } + let count = interned_name.chars().count(); + if count < 3 { + if count == 1 { + self.check_short_ident(ident); + } + return; + } + for existing_name in &self.0.names { + if whitelisted(&interned_name, existing_name.whitelist) { + continue; + } + let mut split_at = None; + match existing_name.len.cmp(&count) { + Ordering::Greater => { + if existing_name.len - count != 1 || levenstein_not_1(&interned_name, &existing_name.interned) { + continue; + } + }, + Ordering::Less => { + if count - existing_name.len != 1 || levenstein_not_1(&existing_name.interned, &interned_name) { + continue; + } + }, + Ordering::Equal => { + let mut interned_chars = interned_name.chars(); + let mut existing_chars = existing_name.interned.chars(); + let first_i = interned_chars.next().expect("we know we have at least one char"); + let first_e = existing_chars.next().expect("we know we have at least one char"); + let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric(); + + if eq_or_numeric((first_i, first_e)) { + let last_i = interned_chars.next_back().expect("we know we have at least two chars"); + let last_e = existing_chars.next_back().expect("we know we have at least two chars"); + if eq_or_numeric((last_i, last_e)) { + if interned_chars + .zip(existing_chars) + .filter(|&ie| !eq_or_numeric(ie)) + .count() + != 1 + { + continue; + } + } else { + let second_last_i = interned_chars + .next_back() + .expect("we know we have at least three chars"); + let second_last_e = existing_chars + .next_back() + .expect("we know we have at least three chars"); + if !eq_or_numeric((second_last_i, second_last_e)) + || second_last_i == '_' + || !interned_chars.zip(existing_chars).all(eq_or_numeric) + { + // allowed similarity foo_x, foo_y + // or too many chars differ (foo_x, boo_y) or (foox, booy) + continue; + } + split_at = interned_name.char_indices().rev().next().map(|(i, _)| i); + } + } else { + let second_i = interned_chars.next().expect("we know we have at least two chars"); + let second_e = existing_chars.next().expect("we know we have at least two chars"); + if !eq_or_numeric((second_i, second_e)) + || second_i == '_' + || !interned_chars.zip(existing_chars).all(eq_or_numeric) + { + // allowed similarity x_foo, y_foo + // or too many chars differ (x_foo, y_boo) or (xfoo, yboo) + continue; + } + split_at = interned_name.chars().next().map(char::len_utf8); + } + }, + } + span_lint_and_then( + self.0.cx, + SIMILAR_NAMES, + ident.span, + "binding's name is too similar to existing binding", + |diag| { + diag.span_note(existing_name.span, "existing binding defined here"); + if let Some(split) = split_at { + diag.span_help( + ident.span, + &format!( + "separate the discriminating character by an \ + underscore like: `{}_{}`", + &interned_name[..split], + &interned_name[split..] + ), + ); + } + }, + ); + return; + } + self.0.names.push(ExistingName { + whitelist: get_whitelist(&interned_name).unwrap_or(&[]), + interned: interned_name, + span: ident.span, + len: count, + }); + } +} + +impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> { + /// ensure scoping rules work + fn apply Fn(&'c mut Self)>(&mut self, f: F) { + let n = self.names.len(); + let single_char_count = self.single_char_names.len(); + f(self); + self.names.truncate(n); + self.single_char_names.truncate(single_char_count); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> { + fn visit_local(&mut self, local: &'tcx Local) { + if let Some(ref init) = local.init { + self.apply(|this| walk_expr(this, &**init)); + } + // add the pattern after the expression because the bindings aren't available + // yet in the init + // expression + SimilarNamesNameVisitor(self).visit_pat(&*local.pat); + } + fn visit_block(&mut self, blk: &'tcx Block) { + self.single_char_names.push(vec![]); + + self.apply(|this| walk_block(this, blk)); + + self.check_single_char_names(); + self.single_char_names.pop(); + } + fn visit_arm(&mut self, arm: &'tcx Arm) { + self.single_char_names.push(vec![]); + + self.apply(|this| { + SimilarNamesNameVisitor(this).visit_pat(&arm.pat); + this.apply(|this| walk_expr(this, &arm.body)); + }); + + self.check_single_char_names(); + self.single_char_names.pop(); + } + fn visit_item(&mut self, _: &Item) { + // do not recurse into inner items + } + fn visit_mac(&mut self, _mac: &MacCall) { + // do not check macs + } +} + +impl EarlyLintPass for NonExpressiveNames { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::Fn(_, ref sig, _, Some(ref blk)) = item.kind { + do_check(self, cx, &item.attrs, &sig.decl, blk); + } + } + + fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &AssocItem) { + if let AssocItemKind::Fn(_, ref sig, _, Some(ref blk)) = item.kind { + do_check(self, cx, &item.attrs, &sig.decl, blk); + } + } +} + +fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) { + if !attr::contains_name(attrs, sym!(test)) { + let mut visitor = SimilarNamesLocalVisitor { + names: Vec::new(), + cx, + lint, + single_char_names: vec![vec![]], + }; + + // initialize with function arguments + for arg in &decl.inputs { + SimilarNamesNameVisitor(&mut visitor).visit_pat(&arg.pat); + } + // walk all other bindings + walk_block(&mut visitor, blk); + + visitor.check_single_char_names(); + } +} + +/// Precondition: `a_name.chars().count() < b_name.chars().count()`. +#[must_use] +fn levenstein_not_1(a_name: &str, b_name: &str) -> bool { + debug_assert!(a_name.chars().count() < b_name.chars().count()); + let mut a_chars = a_name.chars(); + let mut b_chars = b_name.chars(); + while let (Some(a), Some(b)) = (a_chars.next(), b_chars.next()) { + if a == b { + continue; + } + if let Some(b2) = b_chars.next() { + // check if there's just one character inserted + return a != b2 || a_chars.ne(b_chars); + } else { + // tuple + // ntuple + return true; + } + } + // for item in items + true +} diff --git a/src/tools/clippy/clippy_lints/src/open_options.rs b/src/tools/clippy/clippy_lints/src/open_options.rs new file mode 100644 index 0000000000000..9d3b67988dbb5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/open_options.rs @@ -0,0 +1,203 @@ +use crate::utils::{match_type, paths, span_lint, walk_ptrs_ty}; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{Span, Spanned}; + +declare_clippy_lint! { + /// **What it does:** Checks for duplicate open options as well as combinations + /// that make no sense. + /// + /// **Why is this bad?** In the best case, the code will be harder to read than + /// necessary. I don't know the worst case. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().read(true).truncate(true); + /// ``` + pub NONSENSICAL_OPEN_OPTIONS, + correctness, + "nonsensical combination of options for opening a file" +} + +declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OpenOptions { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::MethodCall(ref path, _, ref arguments) = e.kind { + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&arguments[0])); + if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) { + let mut options = Vec::new(); + get_open_options(cx, &arguments[0], &mut options); + check_open_options(cx, &options, e.span); + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Argument { + True, + False, + Unknown, +} + +#[derive(Debug)] +enum OpenOption { + Write, + Read, + Truncate, + Create, + Append, +} + +fn get_open_options(cx: &LateContext<'_, '_>, argument: &Expr<'_>, options: &mut Vec<(OpenOption, Argument)>) { + if let ExprKind::MethodCall(ref path, _, ref arguments) = argument.kind { + let obj_ty = walk_ptrs_ty(cx.tables.expr_ty(&arguments[0])); + + // Only proceed if this is a call on some object of type std::fs::OpenOptions + if match_type(cx, obj_ty, &paths::OPEN_OPTIONS) && arguments.len() >= 2 { + let argument_option = match arguments[1].kind { + ExprKind::Lit(ref span) => { + if let Spanned { + node: LitKind::Bool(lit), + .. + } = *span + { + if lit { + Argument::True + } else { + Argument::False + } + } else { + return; // The function is called with a literal + // which is not a boolean literal. This is theoretically + // possible, but not very likely. + } + }, + _ => Argument::Unknown, + }; + + match &*path.ident.as_str() { + "create" => { + options.push((OpenOption::Create, argument_option)); + }, + "append" => { + options.push((OpenOption::Append, argument_option)); + }, + "truncate" => { + options.push((OpenOption::Truncate, argument_option)); + }, + "read" => { + options.push((OpenOption::Read, argument_option)); + }, + "write" => { + options.push((OpenOption::Write, argument_option)); + }, + _ => (), + } + + get_open_options(cx, &arguments[0], options); + } + } +} + +fn check_open_options(cx: &LateContext<'_, '_>, options: &[(OpenOption, Argument)], span: Span) { + let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false); + let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) = + (false, false, false, false, false); + // This code is almost duplicated (oh, the irony), but I haven't found a way to + // unify it. + + for option in options { + match *option { + (OpenOption::Create, arg) => { + if create { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `create` is called more than once", + ); + } else { + create = true + } + create_arg = create_arg || (arg == Argument::True); + }, + (OpenOption::Append, arg) => { + if append { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `append` is called more than once", + ); + } else { + append = true + } + append_arg = append_arg || (arg == Argument::True); + }, + (OpenOption::Truncate, arg) => { + if truncate { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `truncate` is called more than once", + ); + } else { + truncate = true + } + truncate_arg = truncate_arg || (arg == Argument::True); + }, + (OpenOption::Read, arg) => { + if read { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `read` is called more than once", + ); + } else { + read = true + } + read_arg = read_arg || (arg == Argument::True); + }, + (OpenOption::Write, arg) => { + if write { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "the method `write` is called more than once", + ); + } else { + write = true + } + write_arg = write_arg || (arg == Argument::True); + }, + } + } + + if read && truncate && read_arg && truncate_arg && !(write && write_arg) { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "file opened with `truncate` and `read`", + ); + } + if append && truncate && append_arg && truncate_arg { + span_lint( + cx, + NONSENSICAL_OPEN_OPTIONS, + span, + "file opened with `append` and `truncate`", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs b/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs new file mode 100644 index 0000000000000..66dfa20edb5e7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/option_env_unwrap.rs @@ -0,0 +1,55 @@ +use crate::utils::{is_direct_expn_of, span_lint_and_help}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `option_env!(...).unwrap()` and + /// suggests usage of the `env!` macro. + /// + /// **Why is this bad?** Unwrapping the result of `option_env!` will panic + /// at run-time if the environment variable doesn't exist, whereas `env!` + /// catches it at compile-time. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// let _ = option_env!("HOME").unwrap(); + /// ``` + /// + /// Is better expressed as: + /// + /// ```rust,no_run + /// let _ = env!("HOME"); + /// ``` + pub OPTION_ENV_UNWRAP, + correctness, + "using `option_env!(...).unwrap()` to get environment variable" +} + +declare_lint_pass!(OptionEnvUnwrap => [OPTION_ENV_UNWRAP]); + +impl EarlyLintPass for OptionEnvUnwrap { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if_chain! { + if let ExprKind::MethodCall(path_segment, args) = &expr.kind; + let method_name = path_segment.ident.as_str(); + if method_name == "expect" || method_name == "unwrap"; + if let ExprKind::Call(caller, _) = &args[0].kind; + if is_direct_expn_of(caller.span, "option_env").is_some(); + then { + span_lint_and_help( + cx, + OPTION_ENV_UNWRAP, + expr.span, + "this will panic at run-time if the environment variable doesn't exist at compile-time", + None, + "consider using the `env!` macro instead" + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/overflow_check_conditional.rs b/src/tools/clippy/clippy_lints/src/overflow_check_conditional.rs new file mode 100644 index 0000000000000..b90fdc232e72c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/overflow_check_conditional.rs @@ -0,0 +1,82 @@ +use crate::utils::{span_lint, SpanlessEq}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Detects classic underflow/overflow checks. + /// + /// **Why is this bad?** Most classic C underflow/overflow checks will fail in + /// Rust. Users can use functions like `overflowing_*` and `wrapping_*` instead. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = 1; + /// # let b = 2; + /// a + b < a; + /// ``` + pub OVERFLOW_CHECK_CONDITIONAL, + complexity, + "overflow checks inspired by C which are likely to panic" +} + +declare_lint_pass!(OverflowCheckConditional => [OVERFLOW_CHECK_CONDITIONAL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OverflowCheckConditional { + // a + b < a, a > a + b, a < a - b, a - b > a + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + let eq = |l, r| SpanlessEq::new(cx).eq_path_segment(l, r); + if_chain! { + if let ExprKind::Binary(ref op, ref first, ref second) = expr.kind; + if let ExprKind::Binary(ref op2, ref ident1, ref ident2) = first.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path1)) = ident1.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path2)) = ident2.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path3)) = second.kind; + if eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]); + if cx.tables.expr_ty(ident1).is_integral(); + if cx.tables.expr_ty(ident2).is_integral(); + then { + if let BinOpKind::Lt = op.node { + if let BinOpKind::Add = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "You are trying to use classic C overflow conditions that will fail in Rust."); + } + } + if let BinOpKind::Gt = op.node { + if let BinOpKind::Sub = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "You are trying to use classic C underflow conditions that will fail in Rust."); + } + } + } + } + + if_chain! { + if let ExprKind::Binary(ref op, ref first, ref second) = expr.kind; + if let ExprKind::Binary(ref op2, ref ident1, ref ident2) = second.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path1)) = ident1.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path2)) = ident2.kind; + if let ExprKind::Path(QPath::Resolved(_, ref path3)) = first.kind; + if eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]); + if cx.tables.expr_ty(ident1).is_integral(); + if cx.tables.expr_ty(ident2).is_integral(); + then { + if let BinOpKind::Gt = op.node { + if let BinOpKind::Add = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "You are trying to use classic C overflow conditions that will fail in Rust."); + } + } + if let BinOpKind::Lt = op.node { + if let BinOpKind::Sub = op2.node { + span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, + "You are trying to use classic C underflow conditions that will fail in Rust."); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs new file mode 100644 index 0000000000000..2cd9200ddb252 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs @@ -0,0 +1,154 @@ +use crate::utils::{is_direct_expn_of, is_expn_of, match_function_call, paths, span_lint}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for missing parameters in `panic!`. + /// + /// **Why is this bad?** Contrary to the `format!` family of macros, there are + /// two forms of `panic!`: if there are no parameters given, the first argument + /// is not a format string and used literally. So while `format!("{}")` will + /// fail to compile, `panic!("{}")` will not. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// panic!("This `panic!` is probably missing a parameter there: {}"); + /// ``` + pub PANIC_PARAMS, + style, + "missing parameters in `panic!` calls" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `panic!`. + /// + /// **Why is this bad?** `panic!` will stop the execution of the executable + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// panic!("even with a good reason"); + /// ``` + pub PANIC, + restriction, + "usage of the `panic!` macro" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `unimplemented!`. + /// + /// **Why is this bad?** This macro should not be present in production code + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// unimplemented!(); + /// ``` + pub UNIMPLEMENTED, + restriction, + "`unimplemented!` should not be present in production code" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `todo!`. + /// + /// **Why is this bad?** This macro should not be present in production code + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// todo!(); + /// ``` + pub TODO, + restriction, + "`todo!` should not be present in production code" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `unreachable!`. + /// + /// **Why is this bad?** This macro can cause code to panic + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// unreachable!(); + /// ``` + pub UNREACHABLE, + restriction, + "`unreachable!` should not be present in production code" +} + +declare_lint_pass!(PanicUnimplemented => [PANIC_PARAMS, UNIMPLEMENTED, UNREACHABLE, TODO, PANIC]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PanicUnimplemented { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Block(ref block, _) = expr.kind; + if let Some(ref ex) = block.expr; + if let Some(params) = match_function_call(cx, ex, &paths::BEGIN_PANIC); + if params.len() == 1; + then { + if is_expn_of(expr.span, "unimplemented").is_some() { + let span = get_outer_span(expr); + span_lint(cx, UNIMPLEMENTED, span, + "`unimplemented` should not be present in production code"); + } else if is_expn_of(expr.span, "todo").is_some() { + let span = get_outer_span(expr); + span_lint(cx, TODO, span, + "`todo` should not be present in production code"); + } else if is_expn_of(expr.span, "unreachable").is_some() { + let span = get_outer_span(expr); + span_lint(cx, UNREACHABLE, span, + "`unreachable` should not be present in production code"); + } else if is_expn_of(expr.span, "panic").is_some() { + let span = get_outer_span(expr); + span_lint(cx, PANIC, span, + "`panic` should not be present in production code"); + match_panic(params, expr, cx); + } + } + } + } +} + +fn get_outer_span(expr: &Expr<'_>) -> Span { + if_chain! { + if expr.span.from_expansion(); + let first = expr.span.ctxt().outer_expn_data(); + if first.call_site.from_expansion(); + let second = first.call_site.ctxt().outer_expn_data(); + then { + second.call_site + } else { + expr.span + } + } +} + +fn match_panic(params: &[Expr<'_>], expr: &Expr<'_>, cx: &LateContext<'_, '_>) { + if_chain! { + if let ExprKind::Lit(ref lit) = params[0].kind; + if is_direct_expn_of(expr.span, "panic").is_some(); + if let LitKind::Str(ref string, _) = lit.node; + let string = string.as_str().replace("{{", "").replace("}}", ""); + if let Some(par) = string.find('{'); + if string[par..].contains('}'); + if params[0].span.source_callee().is_none(); + if params[0].span.lo() != params[0].span.hi(); + then { + span_lint(cx, PANIC_PARAMS, params[0].span, + "you probably are missing some parameter in your format string"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs new file mode 100644 index 0000000000000..1445df41c452f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs @@ -0,0 +1,55 @@ +use crate::utils::{is_automatically_derived, span_lint_hir}; +use if_chain::if_chain; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for manual re-implementations of `PartialEq::ne`. + /// + /// **Why is this bad?** `PartialEq::ne` is required to always return the + /// negated result of `PartialEq::eq`, which is exactly what the default + /// implementation does. Therefore, there should never be any need to + /// re-implement it. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// fn eq(&self, other: &Foo) -> bool { true } + /// fn ne(&self, other: &Foo) -> bool { !(self == other) } + /// } + /// ``` + pub PARTIALEQ_NE_IMPL, + complexity, + "re-implementing `PartialEq::ne`" +} + +declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PartialEqNeImpl { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if_chain! { + if let ItemKind::Impl{ of_trait: Some(ref trait_ref), items: impl_items, .. } = item.kind; + if !is_automatically_derived(&*item.attrs); + if let Some(eq_trait) = cx.tcx.lang_items().eq_trait(); + if trait_ref.path.res.def_id() == eq_trait; + then { + for impl_item in impl_items { + if impl_item.ident.name == sym!(ne) { + span_lint_hir( + cx, + PARTIALEQ_NE_IMPL, + impl_item.id.hir_id, + impl_item.span, + "re-implementing `PartialEq::ne` is unnecessary", + ); + } + } + } + }; + } +} diff --git a/src/tools/clippy/clippy_lints/src/path_buf_push_overwrite.rs b/src/tools/clippy/clippy_lints/src/path_buf_push_overwrite.rs new file mode 100644 index 0000000000000..bdbaf2695c8ef --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/path_buf_push_overwrite.rs @@ -0,0 +1,71 @@ +use crate::utils::{match_type, paths, span_lint_and_sugg, walk_ptrs_ty}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::path::{Component, Path}; + +declare_clippy_lint! { + /// **What it does:*** Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) + /// calls on `PathBuf` that can cause overwrites. + /// + /// **Why is this bad?** Calling `push` with a root path at the start can overwrite the + /// previous defined path. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("/bar"); + /// assert_eq!(x, PathBuf::from("/bar")); + /// ``` + /// Could be written: + /// + /// ```rust + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("bar"); + /// assert_eq!(x, PathBuf::from("/foo/bar")); + /// ``` + pub PATH_BUF_PUSH_OVERWRITE, + nursery, + "calling `push` with file system root on `PathBuf` can overwrite it" +} + +declare_lint_pass!(PathBufPushOverwrite => [PATH_BUF_PUSH_OVERWRITE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PathBufPushOverwrite { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind; + if path.ident.name == sym!(push); + if args.len() == 2; + if match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(&args[0])), &paths::PATH_BUF); + if let Some(get_index_arg) = args.get(1); + if let ExprKind::Lit(ref lit) = get_index_arg.kind; + if let LitKind::Str(ref path_lit, _) = lit.node; + if let pushed_path = Path::new(&*path_lit.as_str()); + if let Some(pushed_path_lit) = pushed_path.to_str(); + if pushed_path.has_root(); + if let Some(root) = pushed_path.components().next(); + if root == Component::RootDir; + then { + span_lint_and_sugg( + cx, + PATH_BUF_PUSH_OVERWRITE, + lit.span, + "Calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition", + "try", + format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')), + Applicability::MachineApplicable, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/precedence.rs b/src/tools/clippy/clippy_lints/src/precedence.rs new file mode 100644 index 0000000000000..cc783baa68723 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/precedence.rs @@ -0,0 +1,164 @@ +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +const ODD_FUNCTIONS_WHITELIST: [&str; 14] = [ + "asin", + "asinh", + "atan", + "atanh", + "cbrt", + "fract", + "round", + "signum", + "sin", + "sinh", + "tan", + "tanh", + "to_degrees", + "to_radians", +]; + +declare_clippy_lint! { + /// **What it does:** Checks for operations where precedence may be unclear + /// and suggests to add parentheses. Currently it catches the following: + /// * mixed usage of arithmetic and bit shifting/combining operators without + /// parentheses + /// * a "negative" numeric literal (which is really a unary `-` followed by a + /// numeric literal) + /// followed by a method call + /// + /// **Why is this bad?** Not everyone knows the precedence of those operators by + /// heart, so expressions like these may trip others trying to reason about the + /// code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 + /// * `-1i32.abs()` equals -1, while `(-1i32).abs()` equals 1 + pub PRECEDENCE, + complexity, + "operations where precedence may be unclear" +} + +declare_lint_pass!(Precedence => [PRECEDENCE]); + +impl EarlyLintPass for Precedence { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind { + let span_sugg = |expr: &Expr, sugg, appl| { + span_lint_and_sugg( + cx, + PRECEDENCE, + expr.span, + "operator precedence can trip the unwary", + "consider parenthesizing your expression", + sugg, + appl, + ); + }; + + if !is_bit_op(op) { + return; + } + let mut applicability = Applicability::MachineApplicable; + match (is_arith_expr(left), is_arith_expr(right)) { + (true, true) => { + let sugg = format!( + "({}) {} ({})", + snippet_with_applicability(cx, left.span, "..", &mut applicability), + op.to_string(), + snippet_with_applicability(cx, right.span, "..", &mut applicability) + ); + span_sugg(expr, sugg, applicability); + }, + (true, false) => { + let sugg = format!( + "({}) {} {}", + snippet_with_applicability(cx, left.span, "..", &mut applicability), + op.to_string(), + snippet_with_applicability(cx, right.span, "..", &mut applicability) + ); + span_sugg(expr, sugg, applicability); + }, + (false, true) => { + let sugg = format!( + "{} {} ({})", + snippet_with_applicability(cx, left.span, "..", &mut applicability), + op.to_string(), + snippet_with_applicability(cx, right.span, "..", &mut applicability) + ); + span_sugg(expr, sugg, applicability); + }, + (false, false) => (), + } + } + + if let ExprKind::Unary(UnOp::Neg, ref rhs) = expr.kind { + if let ExprKind::MethodCall(ref path_segment, ref args) = rhs.kind { + let path_segment_str = path_segment.ident.name.as_str(); + if let Some(slf) = args.first() { + if let ExprKind::Lit(ref lit) = slf.kind { + match lit.kind { + LitKind::Int(..) | LitKind::Float(..) => { + if ODD_FUNCTIONS_WHITELIST + .iter() + .any(|odd_function| **odd_function == *path_segment_str) + { + return; + } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + PRECEDENCE, + expr.span, + "unary minus has lower precedence than method call", + "consider adding parentheses to clarify your intent", + format!( + "-({})", + snippet_with_applicability(cx, rhs.span, "..", &mut applicability) + ), + applicability, + ); + }, + _ => (), + } + } + } + } + } + } +} + +fn is_arith_expr(expr: &Expr) -> bool { + match expr.kind { + ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op), + _ => false, + } +} + +#[must_use] +fn is_bit_op(op: BinOpKind) -> bool { + use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr}; + match op { + BitXor | BitAnd | BitOr | Shl | Shr => true, + _ => false, + } +} + +#[must_use] +fn is_arith_op(op: BinOpKind) -> bool { + use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub}; + match op { + Add | Sub | Mul | Div | Rem => true, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs new file mode 100644 index 0000000000000..2cdf96714195a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -0,0 +1,301 @@ +//! Checks for usage of `&Vec[_]` and `&String`. + +use crate::utils::ptr::get_spans; +use crate::utils::{ + is_type_diagnostic_item, match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_sugg, + span_lint_and_then, walk_ptrs_hir_ty, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + BinOpKind, BodyId, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, + Lifetime, MutTy, Mutability, Node, PathSegment, QPath, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::MultiSpan; +use std::borrow::Cow; + +declare_clippy_lint! { + /// **What it does:** This lint checks for function arguments of type `&String` + /// or `&Vec` unless the references are mutable. It will also suggest you + /// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()` + /// calls. + /// + /// **Why is this bad?** Requiring the argument to be of the specific size + /// makes the function less useful for no benefit; slices in the form of `&[T]` + /// or `&str` usually suffice and can be obtained from other types, too. + /// + /// **Known problems:** The lint does not follow data. So if you have an + /// argument `x` and write `let y = x; y.clone()` the lint will not suggest + /// changing that `.clone()` to `.to_owned()`. + /// + /// Other functions called from this function taking a `&String` or `&Vec` + /// argument may also fail to compile if you change the argument. Applying + /// this lint on them will fix the problem, but they may be in other crates. + /// + /// Also there may be `fn(&Vec)`-typed references pointing to your function. + /// If you have them, you will get a compiler error after applying this lint's + /// suggestions. You then have the choice to undo your changes or change the + /// type of the reference. + /// + /// Note that if the function is part of your public interface, there may be + /// other crates referencing it you may not be aware. Carefully deprecate the + /// function before applying the lint suggestions in this case. + /// + /// **Example:** + /// ```ignore + /// fn foo(&Vec) { .. } + /// ``` + pub PTR_ARG, + style, + "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" +} + +declare_clippy_lint! { + /// **What it does:** This lint checks for equality comparisons with `ptr::null` + /// + /// **Why is this bad?** It's easier and more readable to use the inherent + /// `.is_null()` + /// method instead + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// if x == ptr::null { + /// .. + /// } + /// ``` + pub CMP_NULL, + style, + "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead." +} + +declare_clippy_lint! { + /// **What it does:** This lint checks for functions that take immutable + /// references and return + /// mutable ones. + /// + /// **Why is this bad?** This is trivially unsound, as one can create two + /// mutable references + /// from the same (immutable!) source. This + /// [error](https://github.com/rust-lang/rust/issues/39465) + /// actually lead to an interim Rust release 1.15.1. + /// + /// **Known problems:** To be on the conservative side, if there's at least one + /// mutable reference + /// with the output lifetime, this lint will not trigger. In practice, this + /// case is unlikely anyway. + /// + /// **Example:** + /// ```ignore + /// fn foo(&Foo) -> &mut Bar { .. } + /// ``` + pub MUT_FROM_REF, + correctness, + "fns that create mutable refs from immutable ref args" +} + +declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Ptr { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Fn(ref sig, _, body_id) = item.kind { + check_fn(cx, &sig.decl, item.hir_id, Some(body_id)); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(ref sig, body_id) = item.kind { + let parent_item = cx.tcx.hir().get_parent_item(item.hir_id); + if let Some(Node::Item(it)) = cx.tcx.hir().find(parent_item) { + if let ItemKind::Impl { of_trait: Some(_), .. } = it.kind { + return; // ignore trait impls + } + } + check_fn(cx, &sig.decl, item.hir_id, Some(body_id)); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(ref sig, ref trait_method) = item.kind { + let body_id = if let TraitFn::Provided(b) = *trait_method { + Some(b) + } else { + None + }; + check_fn(cx, &sig.decl, item.hir_id, body_id); + } + } + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref op, ref l, ref r) = expr.kind { + if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(l) || is_null_path(r)) { + span_lint( + cx, + CMP_NULL, + expr.span, + "Comparing with null is better expressed by the `.is_null()` method", + ); + } + } + } +} + +#[allow(clippy::too_many_lines)] +fn check_fn(cx: &LateContext<'_, '_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: Option) { + let fn_def_id = cx.tcx.hir().local_def_id(fn_id); + let sig = cx.tcx.fn_sig(fn_def_id); + let fn_ty = sig.skip_binder(); + + for (idx, (arg, ty)) in decl.inputs.iter().zip(fn_ty.inputs()).enumerate() { + if let ty::Ref(_, ty, Mutability::Not) = ty.kind { + if is_type_diagnostic_item(cx, ty, sym!(vec_type)) { + let mut ty_snippet = None; + if_chain! { + if let TyKind::Path(QPath::Resolved(_, ref path)) = walk_ptrs_hir_ty(arg).kind; + if let Some(&PathSegment{args: Some(ref parameters), ..}) = path.segments.last(); + then { + let types: Vec<_> = parameters.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).collect(); + if types.len() == 1 { + ty_snippet = snippet_opt(cx, types[0].span); + } + } + }; + if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) { + span_lint_and_then( + cx, + PTR_ARG, + arg.span, + "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \ + with non-Vec-based slices.", + |diag| { + if let Some(ref snippet) = ty_snippet { + diag.span_suggestion( + arg.span, + "change this to", + format!("&[{}]", snippet), + Applicability::Unspecified, + ); + } + for (clonespan, suggestion) in spans { + diag.span_suggestion( + clonespan, + &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { + Cow::Owned(format!("change `{}` to", x)) + }), + suggestion.into(), + Applicability::Unspecified, + ); + } + }, + ); + } + } else if is_type_diagnostic_item(cx, ty, sym!(string_type)) { + if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) { + span_lint_and_then( + cx, + PTR_ARG, + arg.span, + "writing `&String` instead of `&str` involves a new object where a slice will do.", + |diag| { + diag.span_suggestion(arg.span, "change this to", "&str".into(), Applicability::Unspecified); + for (clonespan, suggestion) in spans { + diag.span_suggestion_short( + clonespan, + &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { + Cow::Owned(format!("change `{}` to", x)) + }), + suggestion.into(), + Applicability::Unspecified, + ); + } + }, + ); + } + } else if match_type(cx, ty, &paths::COW) { + if_chain! { + if let TyKind::Rptr(_, MutTy { ref ty, ..} ) = arg.kind; + if let TyKind::Path(ref path) = ty.kind; + if let QPath::Resolved(None, ref pp) = *path; + if let [ref bx] = *pp.segments; + if let Some(ref params) = bx.args; + if !params.parenthesized; + if let Some(inner) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + then { + let replacement = snippet_opt(cx, inner.span); + if let Some(r) = replacement { + span_lint_and_sugg( + cx, + PTR_ARG, + arg.span, + "using a reference to `Cow` is not recommended.", + "change this to", + "&".to_owned() + &r, + Applicability::Unspecified, + ); + } + } + } + } + } + } + + if let FnRetTy::Return(ref ty) = decl.output { + if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) { + let mut immutables = vec![]; + for (_, ref mutbl, ref argspan) in decl + .inputs + .iter() + .filter_map(|ty| get_rptr_lm(ty)) + .filter(|&(lt, _, _)| lt.name == out.name) + { + if *mutbl == Mutability::Mut { + return; + } + immutables.push(*argspan); + } + if immutables.is_empty() { + return; + } + span_lint_and_then( + cx, + MUT_FROM_REF, + ty.span, + "mutable borrow from immutable input(s)", + |diag| { + let ms = MultiSpan::from_spans(immutables); + diag.span_note(ms, "immutable borrow here"); + }, + ); + } + } +} + +fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> { + if let TyKind::Rptr(ref lt, ref m) = ty.kind { + Some((lt, m.mutbl, ty.span)) + } else { + None + } +} + +fn is_null_path(expr: &Expr<'_>) -> bool { + if let ExprKind::Call(ref pathexp, ref args) = expr.kind { + if args.is_empty() { + if let ExprKind::Path(ref path) = pathexp.kind { + return match_qpath(path, &paths::PTR_NULL) || match_qpath(path, &paths::PTR_NULL_MUT); + } + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs b/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs new file mode 100644 index 0000000000000..ffc59d43750e0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ptr_offset_with_cast.rs @@ -0,0 +1,150 @@ +use crate::utils::{snippet_opt, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::fmt; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of the `offset` pointer method with a `usize` casted to an + /// `isize`. + /// + /// **Why is this bad?** If we’re always increasing the pointer address, we can avoid the numeric + /// cast by using the `add` method instead. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.offset(offset as isize); + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.add(offset); + /// } + /// ``` + pub PTR_OFFSET_WITH_CAST, + complexity, + "unneeded pointer offset cast" +} + +declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PtrOffsetWithCast { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call + let (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) { + Some(call_arg) => call_arg, + None => return, + }; + + // Check if the argument to the method call is a cast from usize + let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) { + Some(cast_lhs_expr) => cast_lhs_expr, + None => return, + }; + + let msg = format!("use of `{}` with a `usize` casted to an `isize`", method); + if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) { + span_lint_and_sugg( + cx, + PTR_OFFSET_WITH_CAST, + expr.span, + &msg, + "try", + sugg, + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, &msg); + } + } +} + +// If the given expression is a cast from a usize, return the lhs of the cast +fn expr_as_cast_from_usize<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Cast(ref cast_lhs_expr, _) = expr.kind { + if is_expr_ty_usize(cx, &cast_lhs_expr) { + return Some(cast_lhs_expr); + } + } + None +} + +// If the given expression is a ptr::offset or ptr::wrapping_offset method call, return the +// receiver, the arg of the method call, and the method. +fn expr_as_ptr_offset_call<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx Expr<'_>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { + if let ExprKind::MethodCall(ref path_segment, _, ref args) = expr.kind { + if is_expr_ty_raw_ptr(cx, &args[0]) { + if path_segment.ident.name == sym!(offset) { + return Some((&args[0], &args[1], Method::Offset)); + } + if path_segment.ident.name == sym!(wrapping_offset) { + return Some((&args[0], &args[1], Method::WrappingOffset)); + } + } + } + None +} + +// Is the type of the expression a usize? +fn is_expr_ty_usize<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>) -> bool { + cx.tables.expr_ty(expr) == cx.tcx.types.usize +} + +// Is the type of the expression a raw pointer? +fn is_expr_ty_raw_ptr<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>) -> bool { + cx.tables.expr_ty(expr).is_unsafe_ptr() +} + +fn build_suggestion<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + method: Method, + receiver_expr: &Expr<'_>, + cast_lhs_expr: &Expr<'_>, +) -> Option { + let receiver = snippet_opt(cx, receiver_expr.span)?; + let cast_lhs = snippet_opt(cx, cast_lhs_expr.span)?; + Some(format!("{}.{}({})", receiver, method.suggestion(), cast_lhs)) +} + +#[derive(Copy, Clone)] +enum Method { + Offset, + WrappingOffset, +} + +impl Method { + #[must_use] + fn suggestion(self) -> &'static str { + match self { + Self::Offset => "add", + Self::WrappingOffset => "wrapping_add", + } + } +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Offset => write!(f, "offset"), + Self::WrappingOffset => write!(f, "wrapping_offset"), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/question_mark.rs b/src/tools/clippy/clippy_lints/src/question_mark.rs new file mode 100644 index 0000000000000..ea654467b8668 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/question_mark.rs @@ -0,0 +1,204 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{def, BindingAnnotation, Block, Expr, ExprKind, MatchSource, PatKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::sugg::Sugg; +use crate::utils::{ + higher, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet_with_applicability, + span_lint_and_sugg, SpanlessEq, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for expressions that could be replaced by the question mark operator. + /// + /// **Why is this bad?** Question mark usage is more idiomatic. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```ignore + /// if option.is_none() { + /// return None; + /// } + /// ``` + /// + /// Could be written: + /// + /// ```ignore + /// option?; + /// ``` + pub QUESTION_MARK, + style, + "checks for expressions that could be replaced by the question mark operator" +} + +declare_lint_pass!(QuestionMark => [QUESTION_MARK]); + +impl QuestionMark { + /// Checks if the given expression on the given context matches the following structure: + /// + /// ```ignore + /// if option.is_none() { + /// return None; + /// } + /// ``` + /// + /// If it matches, it will suggest to use the question mark operator instead + fn check_is_none_and_early_return_none(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let Some((if_expr, body, else_)) = higher::if_block(&expr); + if let ExprKind::MethodCall(segment, _, args) = &if_expr.kind; + if segment.ident.name == sym!(is_none); + if Self::expression_returns_none(cx, body); + if let Some(subject) = args.get(0); + if Self::is_option(cx, subject); + + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability); + let mut replacement: Option = None; + if let Some(else_) = else_ { + if_chain! { + if let ExprKind::Block(block, None) = &else_.kind; + if block.stmts.is_empty(); + if let Some(block_expr) = &block.expr; + if SpanlessEq::new(cx).ignore_fn().eq_expr(subject, block_expr); + then { + replacement = Some(format!("Some({}?)", receiver_str)); + } + } + } else if Self::moves_by_default(cx, subject) + && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) + { + replacement = Some(format!("{}.as_ref()?;", receiver_str)); + } else { + replacement = Some(format!("{}?;", receiver_str)); + } + + if let Some(replacement_str) = replacement { + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this block may be rewritten with the `?` operator", + "replace it with", + replacement_str, + applicability, + ) + } + } + } + } + + fn check_if_let_some_and_early_return_none(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let ExprKind::Match(subject, arms, source) = &expr.kind; + if *source == MatchSource::IfLetDesugar { contains_else_clause: true }; + if Self::is_option(cx, subject); + + if let PatKind::TupleStruct(path1, fields, None) = &arms[0].pat.kind; + if match_qpath(path1, &["Some"]); + if let PatKind::Binding(annot, _, bind, _) = &fields[0].kind; + let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); + + if let ExprKind::Block(block, None) = &arms[0].body.kind; + if block.stmts.is_empty(); + if let Some(trailing_expr) = &block.expr; + if let ExprKind::Path(path) = &trailing_expr.kind; + if match_qpath(path, &[&bind.as_str()]); + + if let PatKind::Wild = arms[1].pat.kind; + if Self::expression_returns_none(cx, arms[1].body); + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, subject.span, "..", &mut applicability); + let replacement = format!( + "{}{}?", + receiver_str, + if by_ref { ".as_ref()" } else { "" }, + ); + + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this if-let-else may be rewritten with the `?` operator", + "replace it with", + replacement, + applicability, + ) + } + } + } + + fn moves_by_default(cx: &LateContext<'_, '_>, expression: &Expr<'_>) -> bool { + let expr_ty = cx.tables.expr_ty(expression); + + !expr_ty.is_copy_modulo_regions(cx.tcx, cx.param_env, expression.span) + } + + fn is_option(cx: &LateContext<'_, '_>, expression: &Expr<'_>) -> bool { + let expr_ty = cx.tables.expr_ty(expression); + + is_type_diagnostic_item(cx, expr_ty, sym!(option_type)) + } + + fn expression_returns_none(cx: &LateContext<'_, '_>, expression: &Expr<'_>) -> bool { + match expression.kind { + ExprKind::Block(ref block, _) => { + if let Some(return_expression) = Self::return_expression(block) { + return Self::expression_returns_none(cx, &return_expression); + } + + false + }, + ExprKind::Ret(Some(ref expr)) => Self::expression_returns_none(cx, expr), + ExprKind::Path(ref qp) => { + if let Res::Def(DefKind::Ctor(def::CtorOf::Variant, def::CtorKind::Const), def_id) = + cx.tables.qpath_res(qp, expression.hir_id) + { + return match_def_path(cx, def_id, &paths::OPTION_NONE); + } + + false + }, + _ => false, + } + } + + fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + // Check if last expression is a return statement. Then, return the expression + if_chain! { + if block.stmts.len() == 1; + if let Some(expr) = block.stmts.iter().last(); + if let StmtKind::Semi(ref expr) = expr.kind; + if let ExprKind::Ret(ret_expr) = expr.kind; + if let Some(ret_expr) = ret_expr; + + then { + return Some(ret_expr); + } + } + + // Check for `return` without a semicolon. + if_chain! { + if block.stmts.is_empty(); + if let Some(ExprKind::Ret(Some(ret_expr))) = block.expr.as_ref().map(|e| &e.kind); + then { + return Some(ret_expr); + } + } + + None + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for QuestionMark { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + Self::check_is_none_and_early_return_none(cx, expr); + Self::check_if_let_some_and_early_return_none(cx, expr); + } +} diff --git a/src/tools/clippy/clippy_lints/src/ranges.rs b/src/tools/clippy/clippy_lints/src/ranges.rs new file mode 100644 index 0000000000000..d7ce2e66d69fb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/ranges.rs @@ -0,0 +1,237 @@ +use if_chain::if_chain; +use rustc_ast::ast::RangeLimits; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +use crate::utils::sugg::Sugg; +use crate::utils::{higher, SpanlessEq}; +use crate::utils::{is_integer_const, snippet, snippet_opt, span_lint, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for zipping a collection with the range of + /// `0.._.len()`. + /// + /// **Why is this bad?** The code is better expressed with `.enumerate()`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let x = vec![1]; + /// x.iter().zip(0..x.len()); + /// ``` + /// Could be written as + /// ```rust + /// # let x = vec![1]; + /// x.iter().enumerate(); + /// ``` + pub RANGE_ZIP_WITH_LEN, + complexity, + "zipping iterator with a range when `enumerate()` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for exclusive ranges where 1 is added to the + /// upper bound, e.g., `x..(y+1)`. + /// + /// **Why is this bad?** The code is more readable with an inclusive range + /// like `x..=y`. + /// + /// **Known problems:** Will add unnecessary pair of parentheses when the + /// expression is not wrapped in a pair but starts with a opening parenthesis + /// and ends with a closing one. + /// I.e., `let _ = (f()+1)..(f()+1)` results in `let _ = ((f()+1)..=f())`. + /// + /// Also in many cases, inclusive ranges are still slower to run than + /// exclusive ranges, because they essentially add an extra branch that + /// LLVM may fail to hoist out of the loop. + /// + /// **Example:** + /// ```rust,ignore + /// for x..(y+1) { .. } + /// ``` + /// Could be written as + /// ```rust,ignore + /// for x..=y { .. } + /// ``` + pub RANGE_PLUS_ONE, + pedantic, + "`x..(y+1)` reads better as `x..=y`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for inclusive ranges where 1 is subtracted from + /// the upper bound, e.g., `x..=(y-1)`. + /// + /// **Why is this bad?** The code is more readable with an exclusive range + /// like `x..y`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// for x..=(y-1) { .. } + /// ``` + /// Could be written as + /// ```rust,ignore + /// for x..y { .. } + /// ``` + pub RANGE_MINUS_ONE, + complexity, + "`x..=(y-1)` reads better as `x..y`" +} + +declare_lint_pass!(Ranges => [ + RANGE_ZIP_WITH_LEN, + RANGE_PLUS_ONE, + RANGE_MINUS_ONE +]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Ranges { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind { + let name = path.ident.as_str(); + if name == "zip" && args.len() == 2 { + let iter = &args[0].kind; + let zip_arg = &args[1]; + if_chain! { + // `.iter()` call + if let ExprKind::MethodCall(ref iter_path, _, ref iter_args ) = *iter; + if iter_path.ident.name == sym!(iter); + // range expression in `.zip()` call: `0..x.len()` + if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(cx, zip_arg); + if is_integer_const(cx, start, 0); + // `.len()` call + if let ExprKind::MethodCall(ref len_path, _, ref len_args) = end.kind; + if len_path.ident.name == sym!(len) && len_args.len() == 1; + // `.iter()` and `.len()` called on same `Path` + if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind; + if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind; + if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments); + then { + span_lint(cx, + RANGE_ZIP_WITH_LEN, + expr.span, + &format!("It is more idiomatic to use `{}.iter().enumerate()`", + snippet(cx, iter_args[0].span, "_"))); + } + } + } + } + + check_exclusive_range_plus_one(cx, expr); + check_inclusive_range_minus_one(cx, expr); + } +} + +// exclusive range plus one: `x..(y+1)` +fn check_exclusive_range_plus_one(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let Some(higher::Range { + start, + end: Some(end), + limits: RangeLimits::HalfOpen + }) = higher::range(cx, expr); + if let Some(y) = y_plus_one(cx, end); + then { + let span = if expr.span.from_expansion() { + expr.span + .ctxt() + .outer_expn_data() + .call_site + } else { + expr.span + }; + span_lint_and_then( + cx, + RANGE_PLUS_ONE, + span, + "an inclusive range would be more readable", + |diag| { + let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string()); + let end = Sugg::hir(cx, y, "y"); + if let Some(is_wrapped) = &snippet_opt(cx, span) { + if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') { + diag.span_suggestion( + span, + "use", + format!("({}..={})", start, end), + Applicability::MaybeIncorrect, + ); + } else { + diag.span_suggestion( + span, + "use", + format!("{}..={}", start, end), + Applicability::MachineApplicable, // snippet + ); + } + } + }, + ); + } + } +} + +// inclusive range minus one: `x..=(y-1)` +fn check_inclusive_range_minus_one(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + if_chain! { + if let Some(higher::Range { start, end: Some(end), limits: RangeLimits::Closed }) = higher::range(cx, expr); + if let Some(y) = y_minus_one(cx, end); + then { + span_lint_and_then( + cx, + RANGE_MINUS_ONE, + expr.span, + "an exclusive range would be more readable", + |diag| { + let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").to_string()); + let end = Sugg::hir(cx, y, "y"); + diag.span_suggestion( + expr.span, + "use", + format!("{}..{}", start, end), + Applicability::MachineApplicable, // snippet + ); + }, + ); + } + } +} + +fn y_plus_one<'t>(cx: &LateContext<'_, '_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { + match expr.kind { + ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref lhs, + ref rhs, + ) => { + if is_integer_const(cx, lhs, 1) { + Some(rhs) + } else if is_integer_const(cx, rhs, 1) { + Some(lhs) + } else { + None + } + }, + _ => None, + } +} + +fn y_minus_one<'t>(cx: &LateContext<'_, '_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { + match expr.kind { + ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, .. + }, + ref lhs, + ref rhs, + ) if is_integer_const(cx, rhs, 1) => Some(lhs), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs new file mode 100644 index 0000000000000..d5cace0c64746 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs @@ -0,0 +1,613 @@ +use crate::utils::{ + fn_has_unsatisfiable_preds, has_drop, is_copy, is_type_diagnostic_item, match_def_path, match_type, paths, + snippet_opt, span_lint_hir, span_lint_hir_and_then, walk_ptrs_ty_depth, +}; +use if_chain::if_chain; +use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{def_id, Body, FnDecl, HirId}; +use rustc_index::bit_set::{BitSet, HybridBitSet}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::{ + self, traversal, + visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _}, +}; +use rustc_middle::ty::{self, fold::TypeVisitor, Ty}; +use rustc_mir::dataflow::BottomValue; +use rustc_mir::dataflow::{Analysis, AnalysisDomain, GenKill, GenKillAnalysis, ResultsCursor}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{BytePos, Span}; +use std::convert::TryFrom; + +macro_rules! unwrap_or_continue { + ($x:expr) => { + match $x { + Some(x) => x, + None => continue, + } + }; +} + +declare_clippy_lint! { + /// **What it does:** Checks for a redundant `clone()` (and its relatives) which clones an owned + /// value that is going to be dropped without further use. + /// + /// **Why is this bad?** It is not always possible for the compiler to eliminate useless + /// allocations and deallocations generated by redundant `clone()`s. + /// + /// **Known problems:** + /// + /// False-negatives: analysis performed by this lint is conservative and limited. + /// + /// **Example:** + /// ```rust + /// # use std::path::Path; + /// # #[derive(Clone)] + /// # struct Foo; + /// # impl Foo { + /// # fn new() -> Self { Foo {} } + /// # } + /// # fn call(x: Foo) {} + /// { + /// let x = Foo::new(); + /// call(x.clone()); + /// call(x.clone()); // this can just pass `x` + /// } + /// + /// ["lorem", "ipsum"].join(" ").to_string(); + /// + /// Path::new("/a/b").join("c").to_path_buf(); + /// ``` + pub REDUNDANT_CLONE, + perf, + "`clone()` of an owned value that is going to be dropped immediately" +} + +declare_lint_pass!(RedundantClone => [REDUNDANT_CLONE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone { + #[allow(clippy::too_many_lines)] + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + _: FnKind<'tcx>, + _: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + let def_id = cx.tcx.hir().body_owner_def_id(body.id()); + + // Building MIR for `fn`s with unsatisfiable preds results in ICE. + if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { + return; + } + + let mir = cx.tcx.optimized_mir(def_id.to_def_id()); + + let maybe_storage_live_result = MaybeStorageLive + .into_engine(cx.tcx, mir, def_id.to_def_id()) + .iterate_to_fixpoint() + .into_results_cursor(mir); + let mut possible_borrower = { + let mut vis = PossibleBorrowerVisitor::new(cx, mir); + vis.visit_body(&mir); + vis.into_map(cx, maybe_storage_live_result) + }; + + for (bb, bbdata) in mir.basic_blocks().iter_enumerated() { + let terminator = bbdata.terminator(); + + if terminator.source_info.span.from_expansion() { + continue; + } + + // Give up on loops + if terminator.successors().any(|s| *s == bb) { + continue; + } + + let (fn_def_id, arg, arg_ty, clone_ret) = + unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); + + let from_borrow = match_def_path(cx, fn_def_id, &paths::CLONE_TRAIT_METHOD) + || match_def_path(cx, fn_def_id, &paths::TO_OWNED_METHOD) + || (match_def_path(cx, fn_def_id, &paths::TO_STRING_METHOD) + && is_type_diagnostic_item(cx, arg_ty, sym!(string_type))); + + let from_deref = !from_borrow + && (match_def_path(cx, fn_def_id, &paths::PATH_TO_PATH_BUF) + || match_def_path(cx, fn_def_id, &paths::OS_STR_TO_OS_STRING)); + + if !from_borrow && !from_deref { + continue; + } + + // `{ cloned = &arg; clone(move cloned); }` or `{ cloned = &arg; to_path_buf(cloned); }` + let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(cx, mir, arg, from_borrow, bb)); + + let loc = mir::Location { + block: bb, + statement_index: bbdata.statements.len(), + }; + + // `Local` to be cloned, and a local of `clone` call's destination + let (local, ret_local) = if from_borrow { + // `res = clone(arg)` can be turned into `res = move arg;` + // if `arg` is the only borrow of `cloned` at this point. + + if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) { + continue; + } + + (cloned, clone_ret) + } else { + // `arg` is a reference as it is `.deref()`ed in the previous block. + // Look into the predecessor block and find out the source of deref. + + let ps = &mir.predecessors()[bb]; + if ps.len() != 1 { + continue; + } + let pred_terminator = mir[ps[0]].terminator(); + + // receiver of the `deref()` call + let (pred_arg, deref_clone_ret) = if_chain! { + if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) = + is_call_with_ref_arg(cx, mir, &pred_terminator.kind); + if res == cloned; + if match_def_path(cx, pred_fn_def_id, &paths::DEREF_TRAIT_METHOD); + if match_type(cx, pred_arg_ty, &paths::PATH_BUF) + || match_type(cx, pred_arg_ty, &paths::OS_STRING); + then { + (pred_arg, res) + } else { + continue; + } + }; + + let (local, cannot_move_out) = + unwrap_or_continue!(find_stmt_assigns_to(cx, mir, pred_arg, true, ps[0])); + let loc = mir::Location { + block: bb, + statement_index: mir.basic_blocks()[bb].statements.len(), + }; + + // This can be turned into `res = move local` if `arg` and `cloned` are not borrowed + // at the last statement: + // + // ``` + // pred_arg = &local; + // cloned = deref(pred_arg); + // arg = &cloned; + // StorageDead(pred_arg); + // res = to_path_buf(cloned); + // ``` + if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) { + continue; + } + + (local, deref_clone_ret) + }; + + let is_temp = mir.local_kind(ret_local) == mir::LocalKind::Temp; + + // 1. `local` can be moved out if it is not used later. + // 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone` + // call anyway. + let (used, consumed_or_mutated) = traversal::ReversePostorder::new(&mir, bb).skip(1).fold( + (false, !is_temp), + |(used, consumed), (tbb, tdata)| { + // Short-circuit + if (used && consumed) || + // Give up on loops + tdata.terminator().successors().any(|s| *s == bb) + { + return (true, true); + } + + let mut vis = LocalUseVisitor { + used: (local, false), + consumed_or_mutated: (ret_local, false), + }; + vis.visit_basic_block_data(tbb, tdata); + (used || vis.used.1, consumed || vis.consumed_or_mutated.1) + }, + ); + + if !used || !consumed_or_mutated { + let span = terminator.source_info.span; + let scope = terminator.source_info.scope; + let node = mir.source_scopes[scope] + .local_data + .as_ref() + .assert_crate_local() + .lint_root; + + if_chain! { + if let Some(snip) = snippet_opt(cx, span); + if let Some(dot) = snip.rfind('.'); + then { + let sugg_span = span.with_lo( + span.lo() + BytePos(u32::try_from(dot).unwrap()) + ); + let mut app = Applicability::MaybeIncorrect; + + let mut call_snip = &snip[dot + 1..]; + // Machine applicable when `call_snip` looks like `foobar()` + if call_snip.ends_with("()") { + call_snip = call_snip[..call_snip.len()-2].trim(); + if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') { + app = Applicability::MachineApplicable; + } + } + + span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| { + diag.span_suggestion( + sugg_span, + "remove this", + String::new(), + app, + ); + if used { + diag.span_note( + span, + "cloned value is neither consumed nor mutated", + ); + } else { + diag.span_note( + span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())), + "this value is dropped without further use", + ); + } + }); + } else { + span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone"); + } + } + } + } + } +} + +/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`. +fn is_call_with_ref_arg<'tcx>( + cx: &LateContext<'_, 'tcx>, + mir: &'tcx mir::Body<'tcx>, + kind: &'tcx mir::TerminatorKind<'tcx>, +) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, mir::Local)> { + if_chain! { + if let mir::TerminatorKind::Call { func, args, destination, .. } = kind; + if args.len() == 1; + if let mir::Operand::Move(mir::Place { local, .. }) = &args[0]; + if let ty::FnDef(def_id, _) = func.ty(&*mir, cx.tcx).kind; + if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx)); + if !is_copy(cx, inner_ty); + then { + Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)?.as_local()?)) + } else { + None + } + } +} + +type CannotMoveOut = bool; + +/// Finds the first `to = (&)from`, and returns +/// ``Some((from, whether `from` cannot be moved out))``. +fn find_stmt_assigns_to<'tcx>( + cx: &LateContext<'_, 'tcx>, + mir: &mir::Body<'tcx>, + to_local: mir::Local, + by_ref: bool, + bb: mir::BasicBlock, +) -> Option<(mir::Local, CannotMoveOut)> { + let rvalue = mir.basic_blocks()[bb].statements.iter().rev().find_map(|stmt| { + if let mir::StatementKind::Assign(box (mir::Place { local, .. }, v)) = &stmt.kind { + return if *local == to_local { Some(v) } else { None }; + } + + None + })?; + + match (by_ref, &*rvalue) { + (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => { + base_local_and_movability(cx, mir, *place) + }, + (false, mir::Rvalue::Ref(_, _, place)) => { + if let [mir::ProjectionElem::Deref] = place.as_ref().projection { + base_local_and_movability(cx, mir, *place) + } else { + None + } + }, + _ => None, + } +} + +/// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself +/// if it is already a `Local`. +/// +/// Also reports whether given `place` cannot be moved out. +fn base_local_and_movability<'tcx>( + cx: &LateContext<'_, 'tcx>, + mir: &mir::Body<'tcx>, + place: mir::Place<'tcx>, +) -> Option<(mir::Local, CannotMoveOut)> { + use rustc_middle::mir::PlaceRef; + + // Dereference. You cannot move things out from a borrowed value. + let mut deref = false; + // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509. + let mut field = false; + // If projection is a slice index then clone can be removed only if the + // underlying type implements Copy + let mut slice = false; + + let PlaceRef { local, mut projection } = place.as_ref(); + while let [base @ .., elem] = projection { + projection = base; + deref |= matches!(elem, mir::ProjectionElem::Deref); + field |= matches!(elem, mir::ProjectionElem::Field(..)) + && has_drop(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty); + slice |= matches!(elem, mir::ProjectionElem::Index(..)) + && !is_copy(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty); + } + + Some((local, deref || field || slice)) +} + +struct LocalUseVisitor { + used: (mir::Local, bool), + consumed_or_mutated: (mir::Local, bool), +} + +impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor { + fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) { + let statements = &data.statements; + for (statement_index, statement) in statements.iter().enumerate() { + self.visit_statement(statement, mir::Location { block, statement_index }); + } + + self.visit_terminator( + data.terminator(), + mir::Location { + block, + statement_index: statements.len(), + }, + ); + } + + fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, _: mir::Location) { + let local = place.local; + + if local == self.used.0 + && !matches!(ctx, PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)) + { + self.used.1 = true; + } + + if local == self.consumed_or_mutated.0 { + match ctx { + PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) + | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => { + self.consumed_or_mutated.1 = true; + }, + _ => {}, + } + } + } +} + +/// Determines liveness of each local purely based on `StorageLive`/`Dead`. +#[derive(Copy, Clone)] +struct MaybeStorageLive; + +impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive { + type Idx = mir::Local; + const NAME: &'static str = "maybe_storage_live"; + + fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize { + body.local_decls.len() + } + + fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet) { + for arg in body.args_iter() { + state.insert(arg); + } + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive { + fn statement_effect(&self, trans: &mut impl GenKill, stmt: &mir::Statement<'tcx>, _: mir::Location) { + match stmt.kind { + mir::StatementKind::StorageLive(l) => trans.gen(l), + mir::StatementKind::StorageDead(l) => trans.kill(l), + _ => (), + } + } + + fn terminator_effect( + &self, + _trans: &mut impl GenKill, + _terminator: &mir::Terminator<'tcx>, + _loc: mir::Location, + ) { + } + + fn call_return_effect( + &self, + _in_out: &mut impl GenKill, + _block: mir::BasicBlock, + _func: &mir::Operand<'tcx>, + _args: &[mir::Operand<'tcx>], + _return_place: mir::Place<'tcx>, + ) { + // Nothing to do when a call returns successfully + } +} + +impl BottomValue for MaybeStorageLive { + /// bottom = dead + const BOTTOM_VALUE: bool = false; +} + +/// Collects the possible borrowers of each local. +/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c` +/// possible borrowers of `a`. +struct PossibleBorrowerVisitor<'a, 'tcx> { + possible_borrower: TransitiveRelation, + body: &'a mir::Body<'tcx>, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'a, 'tcx>, body: &'a mir::Body<'tcx>) -> Self { + Self { + possible_borrower: TransitiveRelation::default(), + cx, + body, + } + } + + fn into_map( + self, + cx: &LateContext<'a, 'tcx>, + maybe_live: ResultsCursor<'tcx, 'tcx, MaybeStorageLive>, + ) -> PossibleBorrowerMap<'a, 'tcx> { + let mut map = FxHashMap::default(); + for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) { + if is_copy(cx, self.body.local_decls[row].ty) { + continue; + } + + let borrowers = self.possible_borrower.reachable_from(&row); + if !borrowers.is_empty() { + let mut bs = HybridBitSet::new_empty(self.body.local_decls.len()); + for &c in borrowers { + if c != mir::Local::from_usize(0) { + bs.insert(c); + } + } + + if !bs.is_empty() { + map.insert(row, bs); + } + } + } + + let bs = BitSet::new_empty(self.body.local_decls.len()); + PossibleBorrowerMap { + map, + maybe_live, + bitset: (bs.clone(), bs), + } + } +} + +impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> { + fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) { + let lhs = place.local; + match rvalue { + mir::Rvalue::Ref(_, _, borrowed) => { + self.possible_borrower.add(borrowed.local, lhs); + }, + other => { + if !ContainsRegion.visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty) { + return; + } + rvalue_locals(other, |rhs| { + if lhs != rhs { + self.possible_borrower.add(rhs, lhs); + } + }); + }, + } + } + + fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) { + if let mir::TerminatorKind::Call { + args, + destination: Some((mir::Place { local: dest, .. }, _)), + .. + } = &terminator.kind + { + // If the call returns something with lifetimes, + // let's conservatively assume the returned value contains lifetime of all the arguments. + // For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`. + if !ContainsRegion.visit_ty(&self.body.local_decls[*dest].ty) { + return; + } + + for op in args { + match op { + mir::Operand::Copy(p) | mir::Operand::Move(p) => { + self.possible_borrower.add(p.local, *dest); + }, + _ => (), + } + } + } + } +} + +struct ContainsRegion; + +impl TypeVisitor<'_> for ContainsRegion { + fn visit_region(&mut self, _: ty::Region<'_>) -> bool { + true + } +} + +fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { + use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use}; + + let mut visit_op = |op: &mir::Operand<'_>| match op { + mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local), + _ => (), + }; + + match rvalue { + Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op), + Aggregate(_, ops) => ops.iter().for_each(visit_op), + BinaryOp(_, lhs, rhs) | CheckedBinaryOp(_, lhs, rhs) => { + visit_op(lhs); + visit_op(rhs); + }, + _ => (), + } +} + +/// Result of `PossibleBorrowerVisitor`. +struct PossibleBorrowerMap<'a, 'tcx> { + /// Mapping `Local -> its possible borrowers` + map: FxHashMap>, + maybe_live: ResultsCursor<'a, 'tcx, MaybeStorageLive>, + // Caches to avoid allocation of `BitSet` on every query + bitset: (BitSet, BitSet), +} + +impl PossibleBorrowerMap<'_, '_> { + /// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`. + fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool { + self.maybe_live.seek_after(at); + + self.bitset.0.clear(); + let maybe_live = &mut self.maybe_live; + if let Some(bitset) = self.map.get(&borrowed) { + for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) { + self.bitset.0.insert(b); + } + } else { + return false; + } + + self.bitset.1.clear(); + for b in borrowers { + self.bitset.1.insert(*b); + } + + self.bitset.0 == self.bitset.1 + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_field_names.rs b/src/tools/clippy/clippy_lints/src/redundant_field_names.rs new file mode 100644 index 0000000000000..b12c3c344ef4c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_field_names.rs @@ -0,0 +1,63 @@ +use crate::utils::span_lint_and_sugg; +use rustc_ast::ast::{Expr, ExprKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for fields in struct literals where shorthands + /// could be used. + /// + /// **Why is this bad?** If the field and variable names are the same, + /// the field name is redundant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let bar: u8 = 123; + /// + /// struct Foo { + /// bar: u8, + /// } + /// + /// let foo = Foo { bar: bar }; + /// ``` + /// the last line can be simplified to + /// ```ignore + /// let foo = Foo { bar }; + /// ``` + pub REDUNDANT_FIELD_NAMES, + style, + "checks for fields in struct literals where shorthands could be used" +} + +declare_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]); + +impl EarlyLintPass for RedundantFieldNames { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Struct(_, ref fields, _) = expr.kind { + for field in fields { + if field.is_shorthand { + continue; + } + if let ExprKind::Path(None, path) = &field.expr.kind { + if path.segments.len() == 1 + && path.segments[0].ident == field.ident + && path.segments[0].args.is_none() + { + span_lint_and_sugg( + cx, + REDUNDANT_FIELD_NAMES, + field.span, + "redundant field names in struct initialization", + "replace it with", + field.ident.to_string(), + Applicability::MachineApplicable, + ); + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_pattern_matching.rs b/src/tools/clippy/clippy_lints/src/redundant_pattern_matching.rs new file mode 100644 index 0000000000000..7ee298e9833f2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_pattern_matching.rs @@ -0,0 +1,216 @@ +use crate::utils::{match_qpath, match_trait_method, paths, snippet, span_lint_and_then}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Lint for redundant pattern matching over `Result` or + /// `Option` + /// + /// **Why is this bad?** It's more concise and clear to just use the proper + /// utility function + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// if let Ok(_) = Ok::(42) {} + /// if let Err(_) = Err::(42) {} + /// if let None = None::<()> {} + /// if let Some(_) = Some(42) {} + /// match Ok::(42) { + /// Ok(_) => true, + /// Err(_) => false, + /// }; + /// ``` + /// + /// The more idiomatic use would be: + /// + /// ```rust + /// if Ok::(42).is_ok() {} + /// if Err::(42).is_err() {} + /// if None::<()>.is_none() {} + /// if Some(42).is_some() {} + /// Ok::(42).is_ok(); + /// ``` + pub REDUNDANT_PATTERN_MATCHING, + style, + "use the proper utility function avoiding an `if let`" +} + +declare_lint_pass!(RedundantPatternMatching => [REDUNDANT_PATTERN_MATCHING]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantPatternMatching { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(op, arms, ref match_source) = &expr.kind { + match match_source { + MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms), + MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"), + MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"), + _ => return, + } + } + } +} + +fn find_sugg_for_if_let<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx Expr<'_>, + op: &Expr<'_>, + arms: &[Arm<'_>], + keyword: &'static str, +) { + let good_method = match arms[0].pat.kind { + PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => { + if let PatKind::Wild = patterns[0].kind { + if match_qpath(path, &paths::RESULT_OK) { + "is_ok()" + } else if match_qpath(path, &paths::RESULT_ERR) { + "is_err()" + } else if match_qpath(path, &paths::OPTION_SOME) { + "is_some()" + } else { + return; + } + } else { + return; + } + }, + + PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => "is_none()", + + _ => return, + }; + + // check that `while_let_on_iterator` lint does not trigger + if_chain! { + if keyword == "while"; + if let ExprKind::MethodCall(method_path, _, _) = op.kind; + if method_path.ident.name == sym!(next); + if match_trait_method(cx, op, &paths::ITERATOR); + then { + return; + } + } + + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + arms[0].pat.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + // while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let expr_span = expr.span; + + // while let ... = ... { ... } + // ^^^ + let op_span = op.span.source_callsite(); + + // while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^ + let span = expr_span.until(op_span.shrink_to_hi()); + diag.span_suggestion( + span, + "try this", + format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method), + Applicability::MachineApplicable, // snippet + ); + }, + ); +} + +fn find_sugg_for_match<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { + if arms.len() == 2 { + let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); + + let found_good_method = match node_pair { + ( + PatKind::TupleStruct(ref path_left, ref patterns_left, _), + PatKind::TupleStruct(ref path_right, ref patterns_right, _), + ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { + find_good_method_for_match( + arms, + path_left, + path_right, + &paths::RESULT_OK, + &paths::RESULT_ERR, + "is_ok()", + "is_err()", + ) + } else { + None + } + }, + (PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right)) + | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _)) + if patterns.len() == 1 => + { + if let PatKind::Wild = patterns[0].kind { + find_good_method_for_match( + arms, + path_left, + path_right, + &paths::OPTION_SOME, + &paths::OPTION_NONE, + "is_some()", + "is_none()", + ) + } else { + None + } + }, + _ => None, + }; + + if let Some(good_method) = found_good_method { + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + expr.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + let span = expr.span.to(op.span); + diag.span_suggestion( + span, + "try this", + format!("{}.{}", snippet(cx, op.span, "_"), good_method), + Applicability::MaybeIncorrect, // snippet + ); + }, + ); + } + } +} + +fn find_good_method_for_match<'a>( + arms: &[Arm<'_>], + path_left: &QPath<'_>, + path_right: &QPath<'_>, + expected_left: &[&str], + expected_right: &[&str], + should_be_left: &'a str, + should_be_right: &'a str, +) -> Option<&'a str> { + let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) { + (&(*arms[0].body).kind, &(*arms[1].body).kind) + } else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) { + (&(*arms[1].body).kind, &(*arms[0].body).kind) + } else { + return None; + }; + + match body_node_pair { + (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { + (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), + (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + _ => None, + }, + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs new file mode 100644 index 0000000000000..6fc07f91660ee --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs @@ -0,0 +1,78 @@ +use crate::utils::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind, VisibilityKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +declare_clippy_lint! { + /// **What it does:** Checks for items declared `pub(crate)` that are not crate visible because they + /// are inside a private module. + /// + /// **Why is this bad?** Writing `pub(crate)` is misleading when it's redundant due to the parent + /// module's visibility. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// mod internal { + /// pub(crate) fn internal_fn() { } + /// } + /// ``` + /// This function is not visible outside the module and it can be declared with `pub` or + /// private visibility + /// ```rust + /// mod internal { + /// pub fn internal_fn() { } + /// } + /// ``` + pub REDUNDANT_PUB_CRATE, + nursery, + "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them." +} + +#[derive(Default)] +pub struct RedundantPubCrate { + is_exported: Vec, +} + +impl_lint_pass!(RedundantPubCrate => [REDUNDANT_PUB_CRATE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantPubCrate { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'tcx>) { + if let VisibilityKind::Crate { .. } = item.vis.node { + if !cx.access_levels.is_exported(item.hir_id) { + if let Some(false) = self.is_exported.last() { + let span = item.span.with_hi(item.ident.span.hi()); + let def_id = cx.tcx.hir().local_def_id(item.hir_id); + let descr = cx.tcx.def_kind(def_id).descr(def_id.to_def_id()); + span_lint_and_then( + cx, + REDUNDANT_PUB_CRATE, + span, + &format!("pub(crate) {} inside private module", descr), + |diag| { + diag.span_suggestion( + item.vis.span, + "consider using", + "pub".to_string(), + Applicability::MachineApplicable, + ); + }, + ) + } + } + } + + if let ItemKind::Mod { .. } = item.kind { + self.is_exported.push(cx.access_levels.is_exported(item.hir_id)); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Mod { .. } = item.kind { + self.is_exported.pop().expect("unbalanced check_item/check_item_post"); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs new file mode 100644 index 0000000000000..c6f57298c2601 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs @@ -0,0 +1,99 @@ +use crate::utils::{snippet, span_lint_and_then}; +use rustc_ast::ast::{Item, ItemKind, Ty, TyKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for constants and statics with an explicit `'static` lifetime. + /// + /// **Why is this bad?** Adding `'static` to every reference can create very + /// complicated types. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] = + /// &[...] + /// static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] = + /// &[...] + /// ``` + /// This code can be rewritten as + /// ```ignore + /// const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] + /// static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] + /// ``` + pub REDUNDANT_STATIC_LIFETIMES, + style, + "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them." +} + +declare_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]); + +impl RedundantStaticLifetimes { + // Recursively visit types + fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) { + match ty.kind { + // Be careful of nested structures (arrays and tuples) + TyKind::Array(ref ty, _) => { + self.visit_type(&*ty, cx, reason); + }, + TyKind::Tup(ref tup) => { + for tup_ty in tup { + self.visit_type(&*tup_ty, cx, reason); + } + }, + // This is what we are looking for ! + TyKind::Rptr(ref optional_lifetime, ref borrow_type) => { + // Match the 'static lifetime + if let Some(lifetime) = *optional_lifetime { + match borrow_type.ty.kind { + TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => { + if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime { + let snip = snippet(cx, borrow_type.ty.span, ""); + let sugg = format!("&{}", snip); + span_lint_and_then( + cx, + REDUNDANT_STATIC_LIFETIMES, + lifetime.ident.span, + reason, + |diag| { + diag.span_suggestion( + ty.span, + "consider removing `'static`", + sugg, + Applicability::MachineApplicable, //snippet + ); + }, + ); + } + }, + _ => {}, + } + } + self.visit_type(&*borrow_type.ty, cx, reason); + }, + TyKind::Slice(ref ty) => { + self.visit_type(ty, cx, reason); + }, + _ => {}, + } + } +} + +impl EarlyLintPass for RedundantStaticLifetimes { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if !item.span.from_expansion() { + if let ItemKind::Const(_, ref var_type, _) = item.kind { + self.visit_type(var_type, cx, "Constants have by default a `'static` lifetime"); + // Don't check associated consts because `'static` cannot be elided on those (issue + // #2438) + } + + if let ItemKind::Static(ref var_type, _, _) = item.kind { + self.visit_type(var_type, cx, "Statics have by default a `'static` lifetime"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/reference.rs b/src/tools/clippy/clippy_lints/src/reference.rs new file mode 100644 index 0000000000000..d5797468e9d53 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/reference.rs @@ -0,0 +1,98 @@ +use crate::utils::{in_macro, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind, UnOp}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `*&` and `*&mut` in expressions. + /// + /// **Why is this bad?** Immediately dereferencing a reference is no-op and + /// makes the code less clear. + /// + /// **Known problems:** Multiple dereference/addrof pairs are not handled so + /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect. + /// + /// **Example:** + /// ```rust,ignore + /// let a = f(*&mut b); + /// let c = *&d; + /// ``` + pub DEREF_ADDROF, + complexity, + "use of `*&` or `*&mut` in an expression" +} + +declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]); + +fn without_parens(mut e: &Expr) -> &Expr { + while let ExprKind::Paren(ref child_e) = e.kind { + e = child_e; + } + e +} + +impl EarlyLintPass for DerefAddrOf { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { + if_chain! { + if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind; + if let ExprKind::AddrOf(_, _, ref addrof_target) = without_parens(deref_target).kind; + if !in_macro(addrof_target.span); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + DEREF_ADDROF, + e.span, + "immediately dereferencing a reference", + "try this", + format!("{}", snippet_with_applicability(cx, addrof_target.span, "_", &mut applicability)), + applicability, + ); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for references in expressions that use + /// auto dereference. + /// + /// **Why is this bad?** The reference is a no-op and is automatically + /// dereferenced by the compiler and makes the code less clear. + /// + /// **Example:** + /// ```rust + /// struct Point(u32, u32); + /// let point = Point(30, 20); + /// let x = (&point).0; + /// ``` + pub REF_IN_DEREF, + complexity, + "Use of reference in auto dereference expression." +} + +declare_lint_pass!(RefInDeref => [REF_IN_DEREF]); + +impl EarlyLintPass for RefInDeref { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { + if_chain! { + if let ExprKind::Field(ref object, _) = e.kind; + if let ExprKind::Paren(ref parened) = object.kind; + if let ExprKind::AddrOf(_, _, ref inner) = parened.kind; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REF_IN_DEREF, + object.span, + "Creating a reference that is immediately dereferenced.", + "try this", + snippet_with_applicability(cx, inner.span, "_", &mut applicability).to_string(), + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/regex.rs b/src/tools/clippy/clippy_lints/src/regex.rs new file mode 100644 index 0000000000000..30084e3e1ffce --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/regex.rs @@ -0,0 +1,263 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{is_expn_of, match_def_path, match_type, paths, span_lint, span_lint_and_help}; +use if_chain::if_chain; +use rustc_ast::ast::{LitKind, StrStyle}; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{Block, BorrowKind, Crate, Expr, ExprKind, HirId}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{BytePos, Span}; +use std::convert::TryFrom; + +declare_clippy_lint! { + /// **What it does:** Checks [regex](https://crates.io/crates/regex) creation + /// (with `Regex::new`,`RegexBuilder::new` or `RegexSet::new`) for correct + /// regex syntax. + /// + /// **Why is this bad?** This will lead to a runtime panic. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// Regex::new("|") + /// ``` + pub INVALID_REGEX, + correctness, + "invalid regular expressions" +} + +declare_clippy_lint! { + /// **What it does:** Checks for trivial [regex](https://crates.io/crates/regex) + /// creation (with `Regex::new`, `RegexBuilder::new` or `RegexSet::new`). + /// + /// **Why is this bad?** Matching the regex can likely be replaced by `==` or + /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str` + /// methods. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// Regex::new("^foobar") + /// ``` + pub TRIVIAL_REGEX, + style, + "trivial regular expressions" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `regex!(_)` which (as of now) is + /// usually slower than `Regex::new(_)` unless called in a loop (which is a bad + /// idea anyway). + /// + /// **Why is this bad?** Performance, at least for now. The macro version is + /// likely to catch up long-term, but for now the dynamic version is faster. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// regex!("foo|bar") + /// ``` + pub REGEX_MACRO, + style, + "use of `regex!(_)` instead of `Regex::new(_)`" +} + +#[derive(Clone, Default)] +pub struct Regex { + spans: FxHashSet, + last: Option, +} + +impl_lint_pass!(Regex => [INVALID_REGEX, REGEX_MACRO, TRIVIAL_REGEX]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Regex { + fn check_crate(&mut self, _: &LateContext<'a, 'tcx>, _: &'tcx Crate<'_>) { + self.spans.clear(); + } + + fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) { + if_chain! { + if self.last.is_none(); + if let Some(ref expr) = block.expr; + if match_type(cx, cx.tables.expr_ty(expr), &paths::REGEX); + if let Some(span) = is_expn_of(expr.span, "regex"); + then { + if !self.spans.contains(&span) { + span_lint(cx, + REGEX_MACRO, + span, + "`regex!(_)` found. \ + Please use `Regex::new(_)`, which is faster for now."); + self.spans.insert(span); + } + self.last = Some(block.hir_id); + } + } + } + + fn check_block_post(&mut self, _: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) { + if self.last.map_or(false, |id| block.hir_id == id) { + self.last = None; + } + } + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref fun, ref args) = expr.kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if args.len() == 1; + if let Some(def_id) = cx.tables.qpath_res(qpath, fun.hir_id).opt_def_id(); + then { + if match_def_path(cx, def_id, &paths::REGEX_NEW) || + match_def_path(cx, def_id, &paths::REGEX_BUILDER_NEW) { + check_regex(cx, &args[0], true); + } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_NEW) || + match_def_path(cx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) { + check_regex(cx, &args[0], false); + } else if match_def_path(cx, def_id, &paths::REGEX_SET_NEW) { + check_set(cx, &args[0], true); + } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_SET_NEW) { + check_set(cx, &args[0], false); + } + } + } + } +} + +#[allow(clippy::cast_possible_truncation)] // truncation very unlikely here +#[must_use] +fn str_span(base: Span, c: regex_syntax::ast::Span, offset: u16) -> Span { + let offset = u32::from(offset); + let end = base.lo() + BytePos(u32::try_from(c.end.offset).expect("offset too large") + offset); + let start = base.lo() + BytePos(u32::try_from(c.start.offset).expect("offset too large") + offset); + assert!(start <= end); + Span::new(start, end, base.ctxt()) +} + +fn const_str<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) -> Option { + constant(cx, cx.tables, e).and_then(|(c, _)| match c { + Constant::Str(s) => Some(s), + _ => None, + }) +} + +fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> { + use regex_syntax::hir::Anchor::{EndText, StartText}; + use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal}; + + let is_literal = |e: &[regex_syntax::hir::Hir]| { + e.iter().all(|e| match *e.kind() { + Literal(_) => true, + _ => false, + }) + }; + + match *s.kind() { + Empty | Anchor(_) => Some("the regex is unlikely to be useful as it is"), + Literal(_) => Some("consider using `str::contains`"), + Alternation(ref exprs) => { + if exprs.iter().all(|e| e.kind().is_empty()) { + Some("the regex is unlikely to be useful as it is") + } else { + None + } + }, + Concat(ref exprs) => match (exprs[0].kind(), exprs[exprs.len() - 1].kind()) { + (&Anchor(StartText), &Anchor(EndText)) if exprs[1..(exprs.len() - 1)].is_empty() => { + Some("consider using `str::is_empty`") + }, + (&Anchor(StartText), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => { + Some("consider using `==` on `str`s") + }, + (&Anchor(StartText), &Literal(_)) if is_literal(&exprs[1..]) => Some("consider using `str::starts_with`"), + (&Literal(_), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => { + Some("consider using `str::ends_with`") + }, + _ if is_literal(exprs) => Some("consider using `str::contains`"), + _ => None, + }, + _ => None, + } +} + +fn check_set<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, utf8: bool) { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind; + if let ExprKind::Array(exprs) = expr.kind; + then { + for expr in exprs { + check_regex(cx, expr, utf8); + } + } + } +} + +fn check_regex<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, utf8: bool) { + let mut parser = regex_syntax::ParserBuilder::new() + .unicode(utf8) + .allow_invalid_utf8(!utf8) + .build(); + + if let ExprKind::Lit(ref lit) = expr.kind { + if let LitKind::Str(ref r, style) = lit.node { + let r = &r.as_str(); + let offset = if let StrStyle::Raw(n) = style { 2 + n } else { 1 }; + match parser.parse(r) { + Ok(r) => { + if let Some(repl) = is_trivial_regex(&r) { + span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl); + } + }, + Err(regex_syntax::Error::Parse(e)) => { + span_lint( + cx, + INVALID_REGEX, + str_span(expr.span, *e.span(), offset), + &format!("regex syntax error: {}", e.kind()), + ); + }, + Err(regex_syntax::Error::Translate(e)) => { + span_lint( + cx, + INVALID_REGEX, + str_span(expr.span, *e.span(), offset), + &format!("regex syntax error: {}", e.kind()), + ); + }, + Err(e) => { + span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e)); + }, + } + } + } else if let Some(r) = const_str(cx, expr) { + match parser.parse(&r) { + Ok(r) => { + if let Some(repl) = is_trivial_regex(&r) { + span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl); + } + }, + Err(regex_syntax::Error::Parse(e)) => { + span_lint( + cx, + INVALID_REGEX, + expr.span, + &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()), + ); + }, + Err(regex_syntax::Error::Translate(e)) => { + span_lint( + cx, + INVALID_REGEX, + expr.span, + &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()), + ); + }, + Err(e) => { + span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e)); + }, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs new file mode 100644 index 0000000000000..5c9117d5b81cd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -0,0 +1,339 @@ +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_ast::visit::FnKind; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::BytePos; + +use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_sugg, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for return statements at the end of a block. + /// + /// **Why is this bad?** Removing the `return` and semicolon will make the code + /// more rusty. + /// + /// **Known problems:** If the computation returning the value borrows a local + /// variable, removing the `return` may run afoul of the borrow checker. + /// + /// **Example:** + /// ```rust + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + /// simplify to + /// ```rust + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + pub NEEDLESS_RETURN, + style, + "using a return statement like `return expr;` where an expression would suffice" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `let`-bindings, which are subsequently + /// returned. + /// + /// **Why is this bad?** It is just extraneous code. Remove it to make your code + /// more rusty. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo() -> String { + /// let x = String::new(); + /// x + /// } + /// ``` + /// instead, use + /// ``` + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + pub LET_AND_RETURN, + style, + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" +} + +declare_clippy_lint! { + /// **What it does:** Checks for unit (`()`) expressions that can be removed. + /// + /// **Why is this bad?** Such expressions add no value, but can make the code + /// less readable. Depending on formatting they can make a `break` or `return` + /// statement look like a function call. + /// + /// **Known problems:** The lint currently misses unit return types in types, + /// e.g., the `F` in `fn generic_unit ()>(f: F) { .. }`. + /// + /// **Example:** + /// ```rust + /// fn return_unit() -> () { + /// () + /// } + /// ``` + pub UNUSED_UNIT, + style, + "needless unit expression" +} + +#[derive(PartialEq, Eq, Copy, Clone)] +enum RetReplacement { + Empty, + Block, +} + +declare_lint_pass!(Return => [NEEDLESS_RETURN, LET_AND_RETURN, UNUSED_UNIT]); + +impl Return { + // Check the final stmt or expr in a block for unnecessary return. + fn check_block_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { + if let Some(stmt) = block.stmts.last() { + match stmt.kind { + ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => { + self.check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); + }, + _ => (), + } + } + } + + // Check a the final expression in a block if it's a return. + fn check_final_expr( + &mut self, + cx: &EarlyContext<'_>, + expr: &ast::Expr, + span: Option, + replacement: RetReplacement, + ) { + match expr.kind { + // simple return is always "bad" + ast::ExprKind::Ret(ref inner) => { + // allow `#[cfg(a)] return a; #[cfg(b)] return b;` + if !expr.attrs.iter().any(attr_is_cfg) { + Self::emit_return_lint( + cx, + span.expect("`else return` is not possible"), + inner.as_ref().map(|i| i.span), + replacement, + ); + } + }, + // a whole block? check it! + ast::ExprKind::Block(ref block, _) => { + self.check_block_return(cx, block); + }, + // an if/if let expr, check both exprs + // note, if without else is going to be a type checking error anyways + // (except for unit type functions) so we don't match it + ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => { + self.check_block_return(cx, ifblock); + self.check_final_expr(cx, elsexpr, None, RetReplacement::Empty); + }, + // a match expr, check all arms + ast::ExprKind::Match(_, ref arms) => { + for arm in arms { + self.check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); + } + }, + _ => (), + } + } + + fn emit_return_lint(cx: &EarlyContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { + match inner_span { + Some(inner_span) => { + if in_external_macro(cx.sess(), inner_span) || inner_span.from_expansion() { + return; + } + + span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { + if let Some(snippet) = snippet_opt(cx, inner_span) { + diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable); + } + }) + }, + None => match replacement { + RetReplacement::Empty => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "remove `return`", + String::new(), + Applicability::MachineApplicable, + ); + }, + RetReplacement::Block => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "replace `return` with an empty block", + "{}".to_string(), + Applicability::MachineApplicable, + ); + }, + }, + } + } + + // Check for "let x = EXPR; x" + fn check_let_return(cx: &EarlyContext<'_>, block: &ast::Block) { + let mut it = block.stmts.iter(); + + // we need both a let-binding stmt and an expr + if_chain! { + if let Some(retexpr) = it.next_back(); + if let ast::StmtKind::Expr(ref retexpr) = retexpr.kind; + if let Some(stmt) = it.next_back(); + if let ast::StmtKind::Local(ref local) = stmt.kind; + // don't lint in the presence of type inference + if local.ty.is_none(); + if local.attrs.is_empty(); + if let Some(ref initexpr) = local.init; + if let ast::PatKind::Ident(_, ident, _) = local.pat.kind; + if let ast::ExprKind::Path(_, ref path) = retexpr.kind; + if match_path_ast(path, &[&*ident.name.as_str()]); + if !in_external_macro(cx.sess(), initexpr.span); + if !in_external_macro(cx.sess(), retexpr.span); + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); + then { + span_lint_and_then( + cx, + LET_AND_RETURN, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(snippet) = snippet_opt(cx, initexpr.span) { + err.multipart_suggestion( + "return the expression directly", + vec![ + (local.span, String::new()), + (retexpr.span, snippet), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, + ); + } + } + } +} + +impl EarlyLintPass for Return { + fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { + match kind { + FnKind::Fn(.., Some(block)) => self.check_block_return(cx, block), + FnKind::Closure(_, body) => self.check_final_expr(cx, body, Some(body.span), RetReplacement::Empty), + FnKind::Fn(.., None) => {}, + } + if_chain! { + if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; + if let ast::TyKind::Tup(ref vals) = ty.kind; + if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span); + then { + let (rspan, appl) = if let Ok(fn_source) = + cx.sess().source_map() + .span_to_snippet(span.with_hi(ty.span.hi())) { + if let Some(rpos) = fn_source.rfind("->") { + #[allow(clippy::cast_possible_truncation)] + (ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)), + Applicability::MachineApplicable) + } else { + (ty.span, Applicability::MaybeIncorrect) + } + } else { + (ty.span, Applicability::MaybeIncorrect) + }; + span_lint_and_sugg( + cx, + UNUSED_UNIT, + rspan, + "unneeded unit return type", + "remove the `-> ()`", + String::new(), + appl, + ); + } + } + } + + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { + Self::check_let_return(cx, block); + if_chain! { + if let Some(ref stmt) = block.stmts.last(); + if let ast::StmtKind::Expr(ref expr) = stmt.kind; + if is_unit_expr(expr) && !stmt.span.from_expansion(); + then { + let sp = expr.span; + span_lint_and_sugg( + cx, + UNUSED_UNIT, + sp, + "unneeded unit expression", + "remove the final `()`", + String::new(), + Applicability::MachineApplicable, + ); + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + match e.kind { + ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => { + if is_unit_expr(expr) && !expr.span.from_expansion() { + span_lint_and_sugg( + cx, + UNUSED_UNIT, + expr.span, + "unneeded `()`", + "remove the `()`", + String::new(), + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } + } +} + +fn attr_is_cfg(attr: &ast::Attribute) -> bool { + attr.meta_item_list().is_some() && attr.check_name(sym!(cfg)) +} + +// get the def site +#[must_use] +fn get_def(span: Span) -> Option { + if span.from_expansion() { + Some(span.ctxt().outer_expn_data().def_site) + } else { + None + } +} + +// is this expr a `()` unit? +fn is_unit_expr(expr: &ast::Expr) -> bool { + if let ast::ExprKind::Tup(ref vals) = expr.kind { + vals.is_empty() + } else { + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/serde_api.rs b/src/tools/clippy/clippy_lints/src/serde_api.rs new file mode 100644 index 0000000000000..6820d1620bd18 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/serde_api.rs @@ -0,0 +1,57 @@ +use crate::utils::{get_trait_def_id, paths, span_lint}; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for mis-uses of the serde API. + /// + /// **Why is this bad?** Serde is very finnicky about how its API should be + /// used, but the type system can't be used to enforce it (yet?). + /// + /// **Known problems:** None. + /// + /// **Example:** Implementing `Visitor::visit_string` but not + /// `Visitor::visit_str`. + pub SERDE_API_MISUSE, + correctness, + "various things that will negatively affect your serde experience" +} + +declare_lint_pass!(SerdeAPI => [SERDE_API_MISUSE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for SerdeAPI { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl { + of_trait: Some(ref trait_ref), + items, + .. + } = item.kind + { + let did = trait_ref.path.res.def_id(); + if let Some(visit_did) = get_trait_def_id(cx, &paths::SERDE_DE_VISITOR) { + if did == visit_did { + let mut seen_str = None; + let mut seen_string = None; + for item in items { + match &*item.ident.as_str() { + "visit_str" => seen_str = Some(item.span), + "visit_string" => seen_string = Some(item.span), + _ => {}, + } + } + if let Some(span) = seen_string { + if seen_str.is_none() { + span_lint( + cx, + SERDE_API_MISUSE, + span, + "you should not implement `visit_string` without also implementing `visit_str`", + ); + } + } + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/shadow.rs b/src/tools/clippy/clippy_lints/src/shadow.rs new file mode 100644 index 0000000000000..11360b0ef8495 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/shadow.rs @@ -0,0 +1,389 @@ +use crate::reexport::Name; +use crate::utils::{contains_name, higher, iter_input_pats, snippet, span_lint_and_then}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + Block, Body, Expr, ExprKind, FnDecl, Guard, HirId, Local, MutTy, Pat, PatKind, Path, QPath, StmtKind, Ty, TyKind, + UnOp, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that shadow other bindings already in + /// scope, while just changing reference level or mutability. + /// + /// **Why is this bad?** Not much, in fact it's a very common pattern in Rust + /// code. Still, some may opt to avoid it in their code base, they can set this + /// lint to `Warn`. + /// + /// **Known problems:** This lint, as the other shadowing related lints, + /// currently only catches very simple patterns. + /// + /// **Example:** + /// ```rust + /// # let x = 1; + /// let x = &x; + /// ``` + pub SHADOW_SAME, + restriction, + "rebinding a name to itself, e.g., `let mut x = &mut x`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that shadow other bindings already in + /// scope, while reusing the original value. + /// + /// **Why is this bad?** Not too much, in fact it's a common pattern in Rust + /// code. Still, some argue that name shadowing like this hurts readability, + /// because a value may be bound to different things depending on position in + /// the code. + /// + /// **Known problems:** This lint, as the other shadowing related lints, + /// currently only catches very simple patterns. + /// + /// **Example:** + /// ```rust + /// let x = 2; + /// let x = x + 1; + /// ``` + /// use different variable name: + /// ```rust + /// let x = 2; + /// let y = x + 1; + /// ``` + pub SHADOW_REUSE, + restriction, + "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for bindings that shadow other bindings already in + /// scope, either without a initialization or with one that does not even use + /// the original value. + /// + /// **Why is this bad?** Name shadowing can hurt readability, especially in + /// large code bases, because it is easy to lose track of the active binding at + /// any place in the code. This can be alleviated by either giving more specific + /// names to bindings or introducing more scopes to contain the bindings. + /// + /// **Known problems:** This lint, as the other shadowing related lints, + /// currently only catches very simple patterns. + /// + /// **Example:** + /// ```rust + /// # let y = 1; + /// # let z = 2; + /// let x = y; + /// let x = z; // shadows the earlier binding + /// ``` + pub SHADOW_UNRELATED, + pedantic, + "rebinding a name without even using the original value" +} + +declare_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Shadow { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + _: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + if in_external_macro(cx.sess(), body.value.span) { + return; + } + check_fn(cx, decl, body); + } +} + +fn check_fn<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>) { + let mut bindings = Vec::with_capacity(decl.inputs.len()); + for arg in iter_input_pats(decl, body) { + if let PatKind::Binding(.., ident, _) = arg.pat.kind { + bindings.push((ident.name, ident.span)) + } + } + check_expr(cx, &body.value, &mut bindings); +} + +fn check_block<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>, bindings: &mut Vec<(Name, Span)>) { + let len = bindings.len(); + for stmt in block.stmts { + match stmt.kind { + StmtKind::Local(ref local) => check_local(cx, local, bindings), + StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => check_expr(cx, e, bindings), + StmtKind::Item(..) => {}, + } + } + if let Some(ref o) = block.expr { + check_expr(cx, o, bindings); + } + bindings.truncate(len); +} + +fn check_local<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, local: &'tcx Local<'_>, bindings: &mut Vec<(Name, Span)>) { + if in_external_macro(cx.sess(), local.span) { + return; + } + if higher::is_from_for_desugar(local) { + return; + } + let Local { + ref pat, + ref ty, + ref init, + span, + .. + } = *local; + if let Some(ref t) = *ty { + check_ty(cx, t, bindings) + } + if let Some(ref o) = *init { + check_expr(cx, o, bindings); + check_pat(cx, pat, Some(o), span, bindings); + } else { + check_pat(cx, pat, None, span, bindings); + } +} + +fn is_binding(cx: &LateContext<'_, '_>, pat_id: HirId) -> bool { + let var_ty = cx.tables.node_type_opt(pat_id); + if let Some(var_ty) = var_ty { + match var_ty.kind { + ty::Adt(..) => false, + _ => true, + } + } else { + false + } +} + +fn check_pat<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + pat: &'tcx Pat<'_>, + init: Option<&'tcx Expr<'_>>, + span: Span, + bindings: &mut Vec<(Name, Span)>, +) { + // TODO: match more stuff / destructuring + match pat.kind { + PatKind::Binding(.., ident, ref inner) => { + let name = ident.name; + if is_binding(cx, pat.hir_id) { + let mut new_binding = true; + for tup in bindings.iter_mut() { + if tup.0 == name { + lint_shadow(cx, name, span, pat.span, init, tup.1); + tup.1 = ident.span; + new_binding = false; + break; + } + } + if new_binding { + bindings.push((name, ident.span)); + } + } + if let Some(ref p) = *inner { + check_pat(cx, p, init, span, bindings); + } + }, + PatKind::Struct(_, pfields, _) => { + if let Some(init_struct) = init { + if let ExprKind::Struct(_, ref efields, _) = init_struct.kind { + for field in pfields { + let name = field.ident.name; + let efield = efields + .iter() + .find_map(|f| if f.ident.name == name { Some(&*f.expr) } else { None }); + check_pat(cx, &field.pat, efield, span, bindings); + } + } else { + for field in pfields { + check_pat(cx, &field.pat, init, span, bindings); + } + } + } else { + for field in pfields { + check_pat(cx, &field.pat, None, span, bindings); + } + } + }, + PatKind::Tuple(inner, _) => { + if let Some(init_tup) = init { + if let ExprKind::Tup(ref tup) = init_tup.kind { + for (i, p) in inner.iter().enumerate() { + check_pat(cx, p, Some(&tup[i]), p.span, bindings); + } + } else { + for p in inner { + check_pat(cx, p, init, span, bindings); + } + } + } else { + for p in inner { + check_pat(cx, p, None, span, bindings); + } + } + }, + PatKind::Box(ref inner) => { + if let Some(initp) = init { + if let ExprKind::Box(ref inner_init) = initp.kind { + check_pat(cx, inner, Some(&**inner_init), span, bindings); + } else { + check_pat(cx, inner, init, span, bindings); + } + } else { + check_pat(cx, inner, init, span, bindings); + } + }, + PatKind::Ref(ref inner, _) => check_pat(cx, inner, init, span, bindings), + // PatVec(Vec>, Option>, Vec>), + _ => (), + } +} + +fn lint_shadow<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + name: Name, + span: Span, + pattern_span: Span, + init: Option<&'tcx Expr<'_>>, + prev_span: Span, +) { + if let Some(expr) = init { + if is_self_shadow(name, expr) { + span_lint_and_then( + cx, + SHADOW_SAME, + span, + &format!( + "`{}` is shadowed by itself in `{}`", + snippet(cx, pattern_span, "_"), + snippet(cx, expr.span, "..") + ), + |diag| { + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } else if contains_name(name, expr) { + span_lint_and_then( + cx, + SHADOW_REUSE, + pattern_span, + &format!( + "`{}` is shadowed by `{}` which reuses the original value", + snippet(cx, pattern_span, "_"), + snippet(cx, expr.span, "..") + ), + |diag| { + diag.span_note(expr.span, "initialization happens here"); + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } else { + span_lint_and_then( + cx, + SHADOW_UNRELATED, + pattern_span, + &format!( + "`{}` is shadowed by `{}`", + snippet(cx, pattern_span, "_"), + snippet(cx, expr.span, "..") + ), + |diag| { + diag.span_note(expr.span, "initialization happens here"); + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } + } else { + span_lint_and_then( + cx, + SHADOW_UNRELATED, + span, + &format!("`{}` shadows a previous declaration", snippet(cx, pattern_span, "_")), + |diag| { + diag.span_note(prev_span, "previous binding is here"); + }, + ); + } +} + +fn check_expr<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>, bindings: &mut Vec<(Name, Span)>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + match expr.kind { + ExprKind::Unary(_, ref e) + | ExprKind::Field(ref e, _) + | ExprKind::AddrOf(_, _, ref e) + | ExprKind::Box(ref e) => check_expr(cx, e, bindings), + ExprKind::Block(ref block, _) | ExprKind::Loop(ref block, _, _) => check_block(cx, block, bindings), + // ExprKind::Call + // ExprKind::MethodCall + ExprKind::Array(v) | ExprKind::Tup(v) => { + for e in v { + check_expr(cx, e, bindings) + } + }, + ExprKind::Match(ref init, arms, _) => { + check_expr(cx, init, bindings); + let len = bindings.len(); + for arm in arms { + check_pat(cx, &arm.pat, Some(&**init), arm.pat.span, bindings); + // This is ugly, but needed to get the right type + if let Some(ref guard) = arm.guard { + match guard { + Guard::If(if_expr) => check_expr(cx, if_expr, bindings), + } + } + check_expr(cx, &arm.body, bindings); + bindings.truncate(len); + } + }, + _ => (), + } +} + +fn check_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: &'tcx Ty<'_>, bindings: &mut Vec<(Name, Span)>) { + match ty.kind { + TyKind::Slice(ref sty) => check_ty(cx, sty, bindings), + TyKind::Array(ref fty, ref anon_const) => { + check_ty(cx, fty, bindings); + check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings); + }, + TyKind::Ptr(MutTy { ty: ref mty, .. }) | TyKind::Rptr(_, MutTy { ty: ref mty, .. }) => { + check_ty(cx, mty, bindings) + }, + TyKind::Tup(tup) => { + for t in tup { + check_ty(cx, t, bindings) + } + }, + TyKind::Typeof(ref anon_const) => check_expr(cx, &cx.tcx.hir().body(anon_const.body).value, bindings), + _ => (), + } +} + +fn is_self_shadow(name: Name, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Box(ref inner) | ExprKind::AddrOf(_, _, ref inner) => is_self_shadow(name, inner), + ExprKind::Block(ref block, _) => { + block.stmts.is_empty() && block.expr.as_ref().map_or(false, |e| is_self_shadow(name, e)) + }, + ExprKind::Unary(op, ref inner) => (UnOp::UnDeref == op) && is_self_shadow(name, inner), + ExprKind::Path(QPath::Resolved(_, ref path)) => path_eq_name(name, path), + _ => false, + } +} + +fn path_eq_name(name: Name, path: &Path<'_>) -> bool { + !path.is_global() && path.segments.len() == 1 && path.segments[0].ident.as_str() == name.as_str() +} diff --git a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs new file mode 100644 index 0000000000000..8d767a7fec88d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs @@ -0,0 +1,62 @@ +use crate::utils::{in_macro, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast::{Item, ItemKind, UseTreeKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::edition::Edition; + +declare_clippy_lint! { + /// **What it does:** Checking for imports with single component use path. + /// + /// **Why is this bad?** Import with single component use path such as `use cratename;` + /// is not necessary, and thus should be removed. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust, ignore + /// use regex; + /// + /// fn main() { + /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + /// } + /// ``` + /// Better as + /// ```rust, ignore + /// fn main() { + /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + /// } + /// ``` + pub SINGLE_COMPONENT_PATH_IMPORTS, + style, + "imports with single component path are redundant" +} + +declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]); + +impl EarlyLintPass for SingleComponentPathImports { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if_chain! { + if !in_macro(item.span); + if cx.sess.opts.edition == Edition::Edition2018; + if !item.vis.node.is_pub(); + if let ItemKind::Use(use_tree) = &item.kind; + if let segments = &use_tree.prefix.segments; + if segments.len() == 1; + if let UseTreeKind::Simple(None, _, _) = use_tree.kind; + then { + span_lint_and_sugg( + cx, + SINGLE_COMPONENT_PATH_IMPORTS, + item.span, + "this import is redundant", + "remove it entirely", + String::new(), + Applicability::MachineApplicable + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs new file mode 100644 index 0000000000000..fb3706be1c213 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs @@ -0,0 +1,324 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{get_enclosing_block, match_qpath, span_lint_and_then, SpanlessEq}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Symbol; + +declare_clippy_lint! { + /// **What it does:** Checks slow zero-filled vector initialization + /// + /// **Why is this bad?** These structures are non-idiomatic and less efficient than simply using + /// `vec![0; len]`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use core::iter::repeat; + /// # let len = 4; + /// let mut vec1 = Vec::with_capacity(len); + /// vec1.resize(len, 0); + /// + /// let mut vec2 = Vec::with_capacity(len); + /// vec2.extend(repeat(0).take(len)) + /// ``` + pub SLOW_VECTOR_INITIALIZATION, + perf, + "slow vector initialization" +} + +declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]); + +/// `VecAllocation` contains data regarding a vector allocated with `with_capacity` and then +/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or +/// `vec = Vec::with_capacity(0)` +struct VecAllocation<'tcx> { + /// Symbol of the local variable name + variable_name: Symbol, + + /// Reference to the expression which allocates the vector + allocation_expr: &'tcx Expr<'tcx>, + + /// Reference to the expression used as argument on `with_capacity` call. This is used + /// to only match slow zero-filling idioms of the same length than vector initialization. + len_expr: &'tcx Expr<'tcx>, +} + +/// Type of slow initialization +enum InitializationType<'tcx> { + /// Extend is a slow initialization with the form `vec.extend(repeat(0).take(..))` + Extend(&'tcx Expr<'tcx>), + + /// Resize is a slow initialization with the form `vec.resize(.., 0)` + Resize(&'tcx Expr<'tcx>), +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for SlowVectorInit { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + // Matches initialization on reassignements. For example: `vec = Vec::with_capacity(100)` + if_chain! { + if let ExprKind::Assign(ref left, ref right, _) = expr.kind; + + // Extract variable name + if let ExprKind::Path(QPath::Resolved(_, ref path)) = left.kind; + if let Some(variable_name) = path.segments.get(0); + + // Extract len argument + if let Some(ref len_arg) = Self::is_vec_with_capacity(right); + + then { + let vi = VecAllocation { + variable_name: variable_name.ident.name, + allocation_expr: right, + len_expr: len_arg, + }; + + Self::search_initialization(cx, vi, expr.hir_id); + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) { + // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` + if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let PatKind::Binding(BindingAnnotation::Mutable, .., variable_name, None) = local.pat.kind; + if let Some(ref init) = local.init; + if let Some(ref len_arg) = Self::is_vec_with_capacity(init); + + then { + let vi = VecAllocation { + variable_name: variable_name.name, + allocation_expr: init, + len_expr: len_arg, + }; + + Self::search_initialization(cx, vi, stmt.hir_id); + } + } + } +} + +impl SlowVectorInit { + /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression + /// of the first argument of `with_capacity` call if it matches or `None` if it does not. + fn is_vec_with_capacity<'tcx>(expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if_chain! { + if let ExprKind::Call(ref func, ref args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["Vec", "with_capacity"]); + if args.len() == 1; + + then { + return Some(&args[0]); + } + } + + None + } + + /// Search initialization for the given vector + fn search_initialization<'tcx>(cx: &LateContext<'_, 'tcx>, vec_alloc: VecAllocation<'tcx>, parent_node: HirId) { + let enclosing_body = get_enclosing_block(cx, parent_node); + + if enclosing_body.is_none() { + return; + } + + let mut v = VectorInitializationVisitor { + cx, + vec_alloc, + slow_expression: None, + initialization_found: false, + }; + + v.visit_block(enclosing_body.unwrap()); + + if let Some(ref allocation_expr) = v.slow_expression { + Self::lint_initialization(cx, allocation_expr, &v.vec_alloc); + } + } + + fn lint_initialization<'tcx>( + cx: &LateContext<'_, 'tcx>, + initialization: &InitializationType<'tcx>, + vec_alloc: &VecAllocation<'_>, + ) { + match initialization { + InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint( + cx, + e, + vec_alloc, + "slow zero-filling initialization", + SLOW_VECTOR_INITIALIZATION, + ), + }; + } + + fn emit_lint<'tcx>( + cx: &LateContext<'_, 'tcx>, + slow_fill: &Expr<'_>, + vec_alloc: &VecAllocation<'_>, + msg: &str, + lint: &'static Lint, + ) { + let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len"); + + span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| { + diag.span_suggestion( + vec_alloc.allocation_expr.span, + "consider replace allocation with", + format!("vec![0; {}]", len_expr), + Applicability::Unspecified, + ); + }); + } +} + +/// `VectorInitializationVisitor` searches for unsafe or slow vector initializations for the given +/// vector. +struct VectorInitializationVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + + /// Contains the information. + vec_alloc: VecAllocation<'tcx>, + + /// Contains the slow initialization expression, if one was found. + slow_expression: Option>, + + /// `true` if the initialization of the vector has been found on the visited block. + initialization_found: bool, +} + +impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { + /// Checks if the given expression is extending a vector with `repeat(0).take(..)` + fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if self.initialization_found; + if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind; + if let ExprKind::Path(ref qpath_subj) = args[0].kind; + if match_qpath(&qpath_subj, &[&*self.vec_alloc.variable_name.as_str()]); + if path.ident.name == sym!(extend); + if let Some(ref extend_arg) = args.get(1); + if self.is_repeat_take(extend_arg); + + then { + self.slow_expression = Some(InitializationType::Extend(expr)); + } + } + } + + /// Checks if the given expression is resizing a vector with 0 + fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + if self.initialization_found; + if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind; + if let ExprKind::Path(ref qpath_subj) = args[0].kind; + if match_qpath(&qpath_subj, &[&*self.vec_alloc.variable_name.as_str()]); + if path.ident.name == sym!(resize); + if let (Some(ref len_arg), Some(fill_arg)) = (args.get(1), args.get(2)); + + // Check that is filled with 0 + if let ExprKind::Lit(ref lit) = fill_arg.kind; + if let LitKind::Int(0, _) = lit.node; + + // Check that len expression is equals to `with_capacity` expression + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); + + then { + self.slow_expression = Some(InitializationType::Resize(expr)); + } + } + } + + /// Returns `true` if give expression is `repeat(0).take(...)` + fn is_repeat_take(&self, expr: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::MethodCall(ref take_path, _, ref take_args) = expr.kind; + if take_path.ident.name == sym!(take); + + // Check that take is applied to `repeat(0)` + if let Some(ref repeat_expr) = take_args.get(0); + if Self::is_repeat_zero(repeat_expr); + + // Check that len expression is equals to `with_capacity` expression + if let Some(ref len_arg) = take_args.get(1); + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); + + then { + return true; + } + } + + false + } + + /// Returns `true` if given expression is `repeat(0)` + fn is_repeat_zero(expr: &Expr<'_>) -> bool { + if_chain! { + if let ExprKind::Call(ref fn_expr, ref repeat_args) = expr.kind; + if let ExprKind::Path(ref qpath_repeat) = fn_expr.kind; + if match_qpath(&qpath_repeat, &["repeat"]); + if let Some(ref repeat_arg) = repeat_args.get(0); + if let ExprKind::Lit(ref lit) = repeat_arg.kind; + if let LitKind::Int(0, _) = lit.node; + + then { + return true + } + } + + false + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + if self.initialization_found { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + self.search_slow_extend_filling(expr); + self.search_slow_resize_filling(expr); + }, + _ => (), + } + + self.initialization_found = false; + } else { + walk_stmt(self, stmt); + } + } + + fn visit_block(&mut self, block: &'tcx Block<'_>) { + if self.initialization_found { + if let Some(ref s) = block.stmts.get(0) { + self.visit_stmt(s) + } + + self.initialization_found = false; + } else { + walk_block(self, block); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // Skip all the expressions previous to the vector initialization + if self.vec_alloc.allocation_expr.hir_id == expr.hir_id { + self.initialization_found = true; + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs new file mode 100644 index 0000000000000..2c51271e312de --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -0,0 +1,200 @@ +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; + +use if_chain::if_chain; + +use crate::utils::SpanlessEq; +use crate::utils::{get_parent_expr, is_allowed, is_type_diagnostic_item, span_lint, span_lint_and_sugg, walk_ptrs_ty}; + +declare_clippy_lint! { + /// **What it does:** Checks for string appends of the form `x = x + y` (without + /// `let`!). + /// + /// **Why is this bad?** It's not really bad, but some people think that the + /// `.push_str(_)` method is more readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let mut x = "Hello".to_owned(); + /// x = x + ", World"; + /// ``` + pub STRING_ADD_ASSIGN, + pedantic, + "using `x = x + ..` where x is a `String` instead of `push_str()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for all instances of `x + _` where `x` is of type + /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not* + /// match. + /// + /// **Why is this bad?** It's not bad in and of itself. However, this particular + /// `Add` implementation is asymmetric (the other operand need not be `String`, + /// but `x` does), while addition as mathematically defined is symmetric, also + /// the `String::push_str(_)` function is a perfectly good replacement. + /// Therefore, some dislike it and wish not to have it in their code. + /// + /// That said, other people think that string addition, having a long tradition + /// in other languages is actually fine, which is why we decided to make this + /// particular lint `allow` by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x = "Hello".to_owned(); + /// x + ", World"; + /// ``` + pub STRING_ADD, + restriction, + "using `x + ..` where x is a `String` instead of `push_str()`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the `as_bytes` method called on string literals + /// that contain only ASCII characters. + /// + /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used + /// instead. They are shorter but less discoverable than `as_bytes()`. + /// + /// **Known Problems:** None. + /// + /// **Example:** + /// ```rust + /// let bs = "a byte string".as_bytes(); + /// ``` + pub STRING_LIT_AS_BYTES, + style, + "calling `as_bytes` on a string literal instead of using a byte string literal" +} + +declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringAdd { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), e.span) { + return; + } + + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref left, + _, + ) = e.kind + { + if is_string(cx, left) { + if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) { + let parent = get_parent_expr(cx, e); + if let Some(p) = parent { + if let ExprKind::Assign(ref target, _, _) = p.kind { + // avoid duplicate matches + if SpanlessEq::new(cx).eq_expr(target, left) { + return; + } + } + } + } + span_lint( + cx, + STRING_ADD, + e.span, + "you added something to a string. Consider using `String::push_str()` instead", + ); + } + } else if let ExprKind::Assign(ref target, ref src, _) = e.kind { + if is_string(cx, target) && is_add(cx, src, target) { + span_lint( + cx, + STRING_ADD_ASSIGN, + e.span, + "you assigned the result of adding something to this string. Consider using \ + `String::push_str()` instead", + ); + } + } + } +} + +fn is_string(cx: &LateContext<'_, '_>, e: &Expr<'_>) -> bool { + is_type_diagnostic_item(cx, walk_ptrs_ty(cx.tables.expr_ty(e)), sym!(string_type)) +} + +fn is_add(cx: &LateContext<'_, '_>, src: &Expr<'_>, target: &Expr<'_>) -> bool { + match src.kind { + ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + ref left, + _, + ) => SpanlessEq::new(cx).eq_expr(target, left), + ExprKind::Block(ref block, _) => { + block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target)) + }, + _ => false, + } +} + +// Max length a b"foo" string can take +const MAX_LENGTH_BYTE_STRING_LIT: usize = 32; + +declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringLitAsBytes { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + use crate::utils::{snippet, snippet_with_applicability}; + use rustc_ast::ast::LitKind; + + if_chain! { + if let ExprKind::MethodCall(path, _, args) = &e.kind; + if path.ident.name == sym!(as_bytes); + if let ExprKind::Lit(lit) = &args[0].kind; + if let LitKind::Str(lit_content, _) = &lit.node; + then { + let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#); + let mut applicability = Applicability::MachineApplicable; + if callsite.starts_with("include_str!") { + span_lint_and_sugg( + cx, + STRING_LIT_AS_BYTES, + e.span, + "calling `as_bytes()` on `include_str!(..)`", + "consider using `include_bytes!(..)` instead", + snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen( + "include_str", + "include_bytes", + 1, + ), + applicability, + ); + } else if lit_content.as_str().is_ascii() + && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT + && !args[0].span.from_expansion() + { + span_lint_and_sugg( + cx, + STRING_LIT_AS_BYTES, + e.span, + "calling `as_bytes()` on a string literal", + "consider using a byte string literal instead", + format!( + "b{}", + snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability) + ), + applicability, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs b/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs new file mode 100644 index 0000000000000..f1e223d9a48c6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs @@ -0,0 +1,205 @@ +use crate::utils::{get_trait_def_id, span_lint, trait_ref_of_method}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Lints for suspicious operations in impls of arithmetic operators, e.g. + /// subtracting elements in an Add impl. + /// + /// **Why this is bad?** This is probably a typo or copy-and-paste error and not intended. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// impl Add for Foo { + /// type Output = Foo; + /// + /// fn add(self, other: Foo) -> Foo { + /// Foo(self.0 - other.0) + /// } + /// } + /// ``` + pub SUSPICIOUS_ARITHMETIC_IMPL, + correctness, + "suspicious use of operators in impl of arithmetic trait" +} + +declare_clippy_lint! { + /// **What it does:** Lints for suspicious operations in impls of OpAssign, e.g. + /// subtracting elements in an AddAssign impl. + /// + /// **Why this is bad?** This is probably a typo or copy-and-paste error and not intended. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// impl AddAssign for Foo { + /// fn add_assign(&mut self, other: Foo) { + /// *self = *self - other; + /// } + /// } + /// ``` + pub SUSPICIOUS_OP_ASSIGN_IMPL, + correctness, + "suspicious use of operators in impl of OpAssign trait" +} + +declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ASSIGN_IMPL]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for SuspiciousImpl { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind { + match binop.node { + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => return, + _ => {}, + } + // Check if the binary expression is part of another bi/unary expression + // or operator assignment as a child node + let mut parent_expr = cx.tcx.hir().get_parent_node(expr.hir_id); + while parent_expr != hir::CRATE_HIR_ID { + if let hir::Node::Expr(e) = cx.tcx.hir().get(parent_expr) { + match e.kind { + hir::ExprKind::Binary(..) + | hir::ExprKind::Unary(hir::UnOp::UnNot, _) + | hir::ExprKind::Unary(hir::UnOp::UnNeg, _) + | hir::ExprKind::AssignOp(..) => return, + _ => {}, + } + } + parent_expr = cx.tcx.hir().get_parent_node(parent_expr); + } + // as a parent node + let mut visitor = BinaryExprVisitor { in_binary_expr: false }; + walk_expr(&mut visitor, expr); + + if visitor.in_binary_expr { + return; + } + + if let Some(impl_trait) = check_binop( + cx, + expr, + binop.node, + &["Add", "Sub", "Mul", "Div"], + &[ + hir::BinOpKind::Add, + hir::BinOpKind::Sub, + hir::BinOpKind::Mul, + hir::BinOpKind::Div, + ], + ) { + span_lint( + cx, + SUSPICIOUS_ARITHMETIC_IMPL, + binop.span, + &format!(r#"Suspicious use of binary operator in `{}` impl"#, impl_trait), + ); + } + + if let Some(impl_trait) = check_binop( + cx, + expr, + binop.node, + &[ + "AddAssign", + "SubAssign", + "MulAssign", + "DivAssign", + "BitAndAssign", + "BitOrAssign", + "BitXorAssign", + "RemAssign", + "ShlAssign", + "ShrAssign", + ], + &[ + hir::BinOpKind::Add, + hir::BinOpKind::Sub, + hir::BinOpKind::Mul, + hir::BinOpKind::Div, + hir::BinOpKind::BitAnd, + hir::BinOpKind::BitOr, + hir::BinOpKind::BitXor, + hir::BinOpKind::Rem, + hir::BinOpKind::Shl, + hir::BinOpKind::Shr, + ], + ) { + span_lint( + cx, + SUSPICIOUS_OP_ASSIGN_IMPL, + binop.span, + &format!(r#"Suspicious use of binary operator in `{}` impl"#, impl_trait), + ); + } + } + } +} + +fn check_binop( + cx: &LateContext<'_, '_>, + expr: &hir::Expr<'_>, + binop: hir::BinOpKind, + traits: &[&'static str], + expected_ops: &[hir::BinOpKind], +) -> Option<&'static str> { + let mut trait_ids = vec![]; + let [krate, module] = crate::utils::paths::OPS_MODULE; + + for &t in traits { + let path = [krate, module, t]; + if let Some(trait_id) = get_trait_def_id(cx, &path) { + trait_ids.push(trait_id); + } else { + return None; + } + } + + // Get the actually implemented trait + let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id); + + if_chain! { + if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn); + if let Some(idx) = trait_ids.iter().position(|&tid| tid == trait_ref.path.res.def_id()); + if binop != expected_ops[idx]; + then{ + return Some(traits[idx]) + } + } + + None +} + +struct BinaryExprVisitor { + in_binary_expr: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for BinaryExprVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + match expr.kind { + hir::ExprKind::Binary(..) + | hir::ExprKind::Unary(hir::UnOp::UnNot, _) + | hir::ExprKind::Unary(hir::UnOp::UnNeg, _) + | hir::ExprKind::AssignOp(..) => self.in_binary_expr = true, + _ => {}, + } + + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs new file mode 100644 index 0000000000000..c52e6a643f2a2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/swap.rs @@ -0,0 +1,263 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{ + differing_macro_contexts, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then, walk_ptrs_ty, + SpanlessEq, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for manual swapping. + /// + /// **Why is this bad?** The `std::mem::swap` function exposes the intent better + /// without deinitializing or copying either variable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut a = 42; + /// let mut b = 1337; + /// + /// let t = b; + /// b = a; + /// a = t; + /// ``` + /// Use std::mem::swap(): + /// ```rust + /// let mut a = 1; + /// let mut b = 2; + /// std::mem::swap(&mut a, &mut b); + /// ``` + pub MANUAL_SWAP, + complexity, + "manual swap of two variables" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `foo = bar; bar = foo` sequences. + /// + /// **Why is this bad?** This looks like a failed attempt to swap. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let mut a = 1; + /// # let mut b = 2; + /// a = b; + /// b = a; + /// ``` + /// If swapping is intended, use `swap()` instead: + /// ```rust + /// # let mut a = 1; + /// # let mut b = 2; + /// std::mem::swap(&mut a, &mut b); + /// ``` + pub ALMOST_SWAPPED, + correctness, + "`foo = bar; bar = foo` sequence" +} + +declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Swap { + fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) { + check_manual_swap(cx, block); + check_suspicious_swap(cx, block); + } +} + +/// Implementation of the `MANUAL_SWAP` lint. +fn check_manual_swap(cx: &LateContext<'_, '_>, block: &Block<'_>) { + for w in block.stmts.windows(3) { + if_chain! { + // let t = foo(); + if let StmtKind::Local(ref tmp) = w[0].kind; + if let Some(ref tmp_init) = tmp.init; + if let PatKind::Binding(.., ident, None) = tmp.pat.kind; + + // foo() = bar(); + if let StmtKind::Semi(ref first) = w[1].kind; + if let ExprKind::Assign(ref lhs1, ref rhs1, _) = first.kind; + + // bar() = t; + if let StmtKind::Semi(ref second) = w[2].kind; + if let ExprKind::Assign(ref lhs2, ref rhs2, _) = second.kind; + if let ExprKind::Path(QPath::Resolved(None, ref rhs2)) = rhs2.kind; + if rhs2.segments.len() == 1; + + if ident.as_str() == rhs2.segments[0].ident.as_str(); + if SpanlessEq::new(cx).ignore_fn().eq_expr(tmp_init, lhs1); + if SpanlessEq::new(cx).ignore_fn().eq_expr(rhs1, lhs2); + then { + if let ExprKind::Field(ref lhs1, _) = lhs1.kind { + if let ExprKind::Field(ref lhs2, _) = lhs2.kind { + if lhs1.hir_id.owner == lhs2.hir_id.owner { + return; + } + } + } + + let mut applicability = Applicability::MachineApplicable; + + let slice = check_for_slice(cx, lhs1, lhs2); + let (replace, what, sugg) = if let Slice::NotSwappable = slice { + return; + } else if let Slice::Swappable(slice, idx1, idx2) = slice { + if let Some(slice) = Sugg::hir_opt(cx, slice) { + ( + false, + format!(" elements of `{}`", slice), + format!( + "{}.swap({}, {})", + slice.maybe_par(), + snippet_with_applicability(cx, idx1.span, "..", &mut applicability), + snippet_with_applicability(cx, idx2.span, "..", &mut applicability), + ), + ) + } else { + (false, String::new(), String::new()) + } + } else if let (Some(first), Some(second)) = (Sugg::hir_opt(cx, lhs1), Sugg::hir_opt(cx, rhs1)) { + ( + true, + format!(" `{}` and `{}`", first, second), + format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()), + ) + } else { + (true, String::new(), String::new()) + }; + + let span = w[0].span.to(second.span); + + span_lint_and_then( + cx, + MANUAL_SWAP, + span, + &format!("this looks like you are swapping{} manually", what), + |diag| { + if !sugg.is_empty() { + diag.span_suggestion( + span, + "try", + sugg, + applicability, + ); + + if replace { + diag.note("or maybe you should use `std::mem::replace`?"); + } + } + } + ); + } + } + } +} + +enum Slice<'a> { + /// `slice.swap(idx1, idx2)` can be used + /// + /// ## Example + /// + /// ```rust + /// # let mut a = vec![0, 1]; + /// let t = a[1]; + /// a[1] = a[0]; + /// a[0] = t; + /// // can be written as + /// a.swap(0, 1); + /// ``` + Swappable(&'a Expr<'a>, &'a Expr<'a>, &'a Expr<'a>), + /// The `swap` function cannot be used. + /// + /// ## Example + /// + /// ```rust + /// # let mut a = [vec![1, 2], vec![3, 4]]; + /// let t = a[0][1]; + /// a[0][1] = a[1][0]; + /// a[1][0] = t; + /// ``` + NotSwappable, + /// Not a slice + None, +} + +/// Checks if both expressions are index operations into "slice-like" types. +fn check_for_slice<'a>(cx: &LateContext<'_, '_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> { + if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind { + if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind { + if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, lhs2) { + let ty = walk_ptrs_ty(cx.tables.expr_ty(lhs1)); + + if matches!(ty.kind, ty::Slice(_)) + || matches!(ty.kind, ty::Array(_, _)) + || is_type_diagnostic_item(cx, ty, sym!(vec_type)) + || is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) + { + return Slice::Swappable(lhs1, idx1, idx2); + } + } else { + return Slice::NotSwappable; + } + } + } + + Slice::None +} + +/// Implementation of the `ALMOST_SWAPPED` lint. +fn check_suspicious_swap(cx: &LateContext<'_, '_>, block: &Block<'_>) { + for w in block.stmts.windows(2) { + if_chain! { + if let StmtKind::Semi(ref first) = w[0].kind; + if let StmtKind::Semi(ref second) = w[1].kind; + if !differing_macro_contexts(first.span, second.span); + if let ExprKind::Assign(ref lhs0, ref rhs0, _) = first.kind; + if let ExprKind::Assign(ref lhs1, ref rhs1, _) = second.kind; + if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs0, rhs1); + if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, rhs0); + then { + let lhs0 = Sugg::hir_opt(cx, lhs0); + let rhs0 = Sugg::hir_opt(cx, rhs0); + let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) { + ( + format!(" `{}` and `{}`", first, second), + first.mut_addr().to_string(), + second.mut_addr().to_string(), + ) + } else { + (String::new(), String::new(), String::new()) + }; + + let span = first.span.to(second.span); + + span_lint_and_then(cx, + ALMOST_SWAPPED, + span, + &format!("this looks like you are trying to swap{}", what), + |diag| { + if !what.is_empty() { + diag.span_suggestion( + span, + "try", + format!( + "std::mem::swap({}, {})", + lhs, + rhs, + ), + Applicability::MaybeIncorrect, + ); + diag.note("or maybe you should use `std::mem::replace`?"); + } + }); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/tabs_in_doc_comments.rs b/src/tools/clippy/clippy_lints/src/tabs_in_doc_comments.rs new file mode 100644 index 0000000000000..7b673e15b764a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/tabs_in_doc_comments.rs @@ -0,0 +1,219 @@ +use crate::utils::span_lint_and_sugg; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::{BytePos, Span}; +use std::convert::TryFrom; + +declare_clippy_lint! { + /// **What it does:** Checks doc comments for usage of tab characters. + /// + /// **Why is this bad?** The rust style-guide promotes spaces instead of tabs for indentation. + /// To keep a consistent view on the source, also doc comments should not have tabs. + /// Also, explaining ascii-diagrams containing tabs can get displayed incorrectly when the + /// display settings of the author and reader differ. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// /// + /// /// Struct to hold two strings: + /// /// - first one + /// /// - second one + /// pub struct DoubleString { + /// /// + /// /// - First String: + /// /// - needs to be inside here + /// first_string: String, + /// /// + /// /// - Second String: + /// /// - needs to be inside here + /// second_string: String, + ///} + /// ``` + /// + /// Will be converted to: + /// ```rust + /// /// + /// /// Struct to hold two strings: + /// /// - first one + /// /// - second one + /// pub struct DoubleString { + /// /// + /// /// - First String: + /// /// - needs to be inside here + /// first_string: String, + /// /// + /// /// - Second String: + /// /// - needs to be inside here + /// second_string: String, + ///} + /// ``` + pub TABS_IN_DOC_COMMENTS, + style, + "using tabs in doc comments is not recommended" +} + +declare_lint_pass!(TabsInDocComments => [TABS_IN_DOC_COMMENTS]); + +impl TabsInDocComments { + fn warn_if_tabs_in_doc(cx: &EarlyContext<'_>, attr: &ast::Attribute) { + if let ast::AttrKind::DocComment(comment) = attr.kind { + let comment = comment.as_str(); + + for (lo, hi) in get_chunks_of_tabs(&comment) { + let new_span = Span::new( + attr.span.lo() + BytePos(lo), + attr.span.lo() + BytePos(hi), + attr.span.ctxt(), + ); + span_lint_and_sugg( + cx, + TABS_IN_DOC_COMMENTS, + new_span, + "using tabs in doc comments is not recommended", + "consider using four spaces per tab", + " ".repeat((hi - lo) as usize), + Applicability::MaybeIncorrect, + ); + } + } + } +} + +impl EarlyLintPass for TabsInDocComments { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attribute: &ast::Attribute) { + Self::warn_if_tabs_in_doc(cx, &attribute); + } +} + +/// +/// scans the string for groups of tabs and returns the start(inclusive) and end positions +/// (exclusive) of all groups +/// e.g. "sd\tasd\t\taa" will be converted to [(2, 3), (6, 8)] as +/// 012 3456 7 89 +/// ^-^ ^---^ +fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> { + let line_length_way_to_long = "doc comment longer than 2^32 chars"; + let mut spans: Vec<(u32, u32)> = vec![]; + let mut current_start: u32 = 0; + + // tracker to decide if the last group of tabs is not closed by a non-tab character + let mut is_active = false; + + let chars_array: Vec<_> = the_str.chars().collect(); + + if chars_array == vec!['\t'] { + return vec![(0, 1)]; + } + + for (index, arr) in chars_array.windows(2).enumerate() { + let index = u32::try_from(index).expect(line_length_way_to_long); + match arr { + ['\t', '\t'] => { + // either string starts with double tab, then we have to set it active, + // otherwise is_active is true anyway + is_active = true; + }, + [_, '\t'] => { + // as ['\t', '\t'] is excluded, this has to be a start of a tab group, + // set indices accordingly + is_active = true; + current_start = index + 1; + }, + ['\t', _] => { + // this now has to be an end of the group, hence we have to push a new tuple + is_active = false; + spans.push((current_start, index + 1)); + }, + _ => {}, + } + } + + // only possible when tabs are at the end, insert last group + if is_active { + spans.push(( + current_start, + u32::try_from(the_str.chars().count()).expect(line_length_way_to_long), + )); + } + + spans +} + +#[cfg(test)] +mod tests_for_get_chunks_of_tabs { + use super::get_chunks_of_tabs; + + #[test] + fn test_empty_string() { + let res = get_chunks_of_tabs(""); + + assert_eq!(res, vec![]); + } + + #[test] + fn test_simple() { + let res = get_chunks_of_tabs("sd\t\t\taa"); + + assert_eq!(res, vec![(2, 5)]); + } + + #[test] + fn test_only_t() { + let res = get_chunks_of_tabs("\t\t"); + + assert_eq!(res, vec![(0, 2)]); + } + + #[test] + fn test_only_one_t() { + let res = get_chunks_of_tabs("\t"); + + assert_eq!(res, vec![(0, 1)]); + } + + #[test] + fn test_double() { + let res = get_chunks_of_tabs("sd\tasd\t\taa"); + + assert_eq!(res, vec![(2, 3), (6, 8)]); + } + + #[test] + fn test_start() { + let res = get_chunks_of_tabs("\t\taa"); + + assert_eq!(res, vec![(0, 2)]); + } + + #[test] + fn test_end() { + let res = get_chunks_of_tabs("aa\t\t"); + + assert_eq!(res, vec![(2, 4)]); + } + + #[test] + fn test_start_single() { + let res = get_chunks_of_tabs("\taa"); + + assert_eq!(res, vec![(0, 1)]); + } + + #[test] + fn test_end_single() { + let res = get_chunks_of_tabs("aa\t"); + + assert_eq!(res, vec![(2, 3)]); + } + + #[test] + fn test_no_tabs() { + let res = get_chunks_of_tabs("dsfs"); + + assert_eq!(res, vec![]); + } +} diff --git a/src/tools/clippy/clippy_lints/src/temporary_assignment.rs b/src/tools/clippy/clippy_lints/src/temporary_assignment.rs new file mode 100644 index 0000000000000..bbb883aaf3287 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/temporary_assignment.rs @@ -0,0 +1,53 @@ +use crate::utils::{is_adjusted, span_lint}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for construction of a structure or tuple just to + /// assign a value in it. + /// + /// **Why is this bad?** Readability. If the structure is only created to be + /// updated, why not write the structure you want in the first place? + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// (0, 0).0 = 1 + /// ``` + pub TEMPORARY_ASSIGNMENT, + complexity, + "assignments to temporaries" +} + +fn is_temporary(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + match &expr.kind { + ExprKind::Struct(..) | ExprKind::Tup(..) => true, + ExprKind::Path(qpath) => { + if let Res::Def(DefKind::Const, ..) = cx.tables.qpath_res(qpath, expr.hir_id) { + true + } else { + false + } + }, + _ => false, + } +} + +declare_lint_pass!(TemporaryAssignment => [TEMPORARY_ASSIGNMENT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TemporaryAssignment { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Assign(target, ..) = &expr.kind { + let mut base = target; + while let ExprKind::Field(f, _) | ExprKind::Index(f, _) = &base.kind { + base = f; + } + if is_temporary(cx, base) && !is_adjusted(cx, base) { + span_lint(cx, TEMPORARY_ASSIGNMENT, expr.span, "assignment to temporary"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs b/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs new file mode 100644 index 0000000000000..c6302ca03d918 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/to_digit_is_some.rs @@ -0,0 +1,94 @@ +use crate::utils::{match_def_path, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `.to_digit(..).is_some()` on `char`s. + /// + /// **Why is this bad?** This is a convoluted way of checking if a `char` is a digit. It's + /// more straight forward to use the dedicated `is_digit` method. + /// + /// **Example:** + /// ```rust + /// # let c = 'c'; + /// # let radix = 10; + /// let is_digit = c.to_digit(radix).is_some(); + /// ``` + /// can be written as: + /// ``` + /// # let c = 'c'; + /// # let radix = 10; + /// let is_digit = c.is_digit(radix); + /// ``` + pub TO_DIGIT_IS_SOME, + style, + "`char.is_digit()` is clearer" +} + +declare_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ToDigitIsSome { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + if let hir::ExprKind::MethodCall(is_some_path, _, is_some_args) = &expr.kind; + if is_some_path.ident.name.as_str() == "is_some"; + if let [to_digit_expr] = &**is_some_args; + then { + let match_result = match &to_digit_expr.kind { + hir::ExprKind::MethodCall(to_digits_path, _, to_digit_args) => { + if_chain! { + if let [char_arg, radix_arg] = &**to_digit_args; + if to_digits_path.ident.name.as_str() == "to_digit"; + let char_arg_ty = cx.tables.expr_ty_adjusted(char_arg); + if char_arg_ty.kind == ty::Char; + then { + Some((true, char_arg, radix_arg)) + } else { + None + } + } + } + hir::ExprKind::Call(to_digits_call, to_digit_args) => { + if_chain! { + if let [char_arg, radix_arg] = &**to_digit_args; + if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind; + if let to_digits_call_res = cx.tables.qpath_res(to_digits_path, to_digits_call.hir_id); + if let Some(to_digits_def_id) = to_digits_call_res.opt_def_id(); + if match_def_path(cx, to_digits_def_id, &["core", "char", "methods", "", "to_digit"]); + then { + Some((false, char_arg, radix_arg)) + } else { + None + } + } + } + _ => None + }; + + if let Some((is_method_call, char_arg, radix_arg)) = match_result { + let mut applicability = Applicability::MachineApplicable; + let char_arg_snip = snippet_with_applicability(cx, char_arg.span, "_", &mut applicability); + let radix_snip = snippet_with_applicability(cx, radix_arg.span, "_", &mut applicability); + + span_lint_and_sugg( + cx, + TO_DIGIT_IS_SOME, + expr.span, + "use of `.to_digit(..).is_some()`", + "try this", + if is_method_call { + format!("{}.is_digit({})", char_arg_snip, radix_snip) + } else { + format!("char::is_digit({}, {})", char_arg_snip, radix_snip) + }, + applicability, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/trait_bounds.rs b/src/tools/clippy/clippy_lints/src/trait_bounds.rs new file mode 100644 index 0000000000000..67121729663c6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/trait_bounds.rs @@ -0,0 +1,86 @@ +use crate::utils::{in_macro, snippet, snippet_with_applicability, span_lint_and_help, SpanlessHash}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::{GenericBound, Generics, WherePredicate}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +#[derive(Copy, Clone)] +pub struct TraitBounds; + +declare_clippy_lint! { + /// **What it does:** This lint warns about unnecessary type repetitions in trait bounds + /// + /// **Why is this bad?** Repeating the type for every bound makes the code + /// less readable than combining the bounds + /// + /// **Example:** + /// ```rust + /// pub fn foo(t: T) where T: Copy, T: Clone {} + /// ``` + /// + /// Could be written as: + /// + /// ```rust + /// pub fn foo(t: T) where T: Copy + Clone {} + /// ``` + pub TYPE_REPETITION_IN_BOUNDS, + pedantic, + "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" +} + +impl_lint_pass!(TraitBounds => [TYPE_REPETITION_IN_BOUNDS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TraitBounds { + fn check_generics(&mut self, cx: &LateContext<'a, 'tcx>, gen: &'tcx Generics<'_>) { + if in_macro(gen.span) { + return; + } + let hash = |ty| -> u64 { + let mut hasher = SpanlessHash::new(cx, cx.tables); + hasher.hash_ty(ty); + hasher.finish() + }; + let mut map = FxHashMap::default(); + let mut applicability = Applicability::MaybeIncorrect; + for bound in gen.where_clause.predicates { + if let WherePredicate::BoundPredicate(ref p) = bound { + let h = hash(&p.bounded_ty); + if let Some(ref v) = map.insert(h, p.bounds.iter().collect::>()) { + let mut hint_string = format!( + "consider combining the bounds: `{}:", + snippet(cx, p.bounded_ty.span, "_") + ); + for b in v.iter() { + if let GenericBound::Trait(ref poly_trait_ref, _) = b { + let path = &poly_trait_ref.trait_ref.path; + hint_string.push_str(&format!( + " {} +", + snippet_with_applicability(cx, path.span, "..", &mut applicability) + )); + } + } + for b in p.bounds.iter() { + if let GenericBound::Trait(ref poly_trait_ref, _) = b { + let path = &poly_trait_ref.trait_ref.path; + hint_string.push_str(&format!( + " {} +", + snippet_with_applicability(cx, path.span, "..", &mut applicability) + )); + } + } + hint_string.truncate(hint_string.len() - 2); + hint_string.push('`'); + span_lint_and_help( + cx, + TYPE_REPETITION_IN_BOUNDS, + p.span, + "this type has already been used as a bound predicate", + None, + &hint_string, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute.rs b/src/tools/clippy/clippy_lints/src/transmute.rs new file mode 100644 index 0000000000000..e24d2c4f495db --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute.rs @@ -0,0 +1,653 @@ +use crate::utils::{ + is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg, + span_lint_and_then, sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, TyKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use std::borrow::Cow; + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes that can't ever be correct on any + /// architecture. + /// + /// **Why is this bad?** It's basically guaranteed to be undefined behaviour. + /// + /// **Known problems:** When accessing C, users might want to store pointer + /// sized objects in `extradata` arguments to save an allocation. + /// + /// **Example:** + /// ```ignore + /// let ptr: *const T = core::intrinsics::transmute('x') + /// ``` + pub WRONG_TRANSMUTE, + correctness, + "transmutes that are confusing at best, undefined behaviour at worst and always useless" +} + +// FIXME: Move this to `complexity` again, after #5343 is fixed +declare_clippy_lint! { + /// **What it does:** Checks for transmutes to the original type of the object + /// and transmutes that could be a cast. + /// + /// **Why is this bad?** Readability. The code tricks people into thinking that + /// something complex is going on. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s + /// ``` + pub USELESS_TRANSMUTE, + nursery, + "transmutes that have the same to and from types or could be a cast/coercion" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes between a type `T` and `*T`. + /// + /// **Why is this bad?** It's easy to mistakenly transmute between a type and a + /// pointer to that type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// core::intrinsics::transmute(t) // where the result type is the same as + /// // `*t` or `&t`'s + /// ``` + pub CROSSPOINTER_TRANSMUTE, + complexity, + "transmutes that have to or from types that are a pointer to the other" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a pointer to a reference. + /// + /// **Why is this bad?** This can always be rewritten with `&` and `*`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// unsafe { + /// let _: &T = std::mem::transmute(p); // where p: *const T + /// } + /// + /// // can be written: + /// let _: &T = &*p; + /// ``` + pub TRANSMUTE_PTR_TO_REF, + complexity, + "transmutes from a pointer to a reference type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a `char`. + /// + /// **Why is this bad?** Not every integer is a Unicode scalar value. + /// + /// **Known problems:** + /// - [`from_u32`] which this lint suggests using is slower than `transmute` + /// as it needs to validate the input. + /// If you are certain that the input is always a valid Unicode scalar value, + /// use [`from_u32_unchecked`] which is as fast as `transmute` + /// but has a semantically meaningful name. + /// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`. + /// + /// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html + /// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html + /// + /// **Example:** + /// ```rust + /// let x = 1_u32; + /// unsafe { + /// let _: char = std::mem::transmute(x); // where x: u32 + /// } + /// + /// // should be: + /// let _ = std::char::from_u32(x).unwrap(); + /// ``` + pub TRANSMUTE_INT_TO_CHAR, + complexity, + "transmutes from an integer to a `char`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a `&[u8]` to a `&str`. + /// + /// **Why is this bad?** Not every byte slice is a valid UTF-8 string. + /// + /// **Known problems:** + /// - [`from_utf8`] which this lint suggests using is slower than `transmute` + /// as it needs to validate the input. + /// If you are certain that the input is always a valid UTF-8, + /// use [`from_utf8_unchecked`] which is as fast as `transmute` + /// but has a semantically meaningful name. + /// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`. + /// + /// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html + /// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html + /// + /// **Example:** + /// ```rust + /// let b: &[u8] = &[1_u8, 2_u8]; + /// unsafe { + /// let _: &str = std::mem::transmute(b); // where b: &[u8] + /// } + /// + /// // should be: + /// let _ = std::str::from_utf8(b).unwrap(); + /// ``` + pub TRANSMUTE_BYTES_TO_STR, + complexity, + "transmutes from a `&[u8]` to a `&str`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a `bool`. + /// + /// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1_u8; + /// unsafe { + /// let _: bool = std::mem::transmute(x); // where x: u8 + /// } + /// + /// // should be: + /// let _: bool = x != 0; + /// ``` + pub TRANSMUTE_INT_TO_BOOL, + complexity, + "transmutes from an integer to a `bool`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a float. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: f32 = std::mem::transmute(1_u32); // where x: u32 + /// } + /// + /// // should be: + /// let _: f32 = f32::from_bits(1_u32); + /// ``` + pub TRANSMUTE_INT_TO_FLOAT, + complexity, + "transmutes from an integer to a float" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a float to an integer. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: u32 = std::mem::transmute(1f32); + /// } + /// + /// // should be: + /// let _: u32 = 1f32.to_bits(); + /// ``` + pub TRANSMUTE_FLOAT_TO_INT, + complexity, + "transmutes from a float to an integer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a pointer to a pointer, or + /// from a reference to a reference. + /// + /// **Why is this bad?** Transmutes are dangerous, and these can instead be + /// written as casts. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let ptr = &1u32 as *const u32; + /// unsafe { + /// // pointer-to-pointer transmute + /// let _: *const f32 = std::mem::transmute(ptr); + /// // ref-ref transmute + /// let _: &f32 = std::mem::transmute(&1u32); + /// } + /// // These can be respectively written: + /// let _ = ptr as *const f32; + /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) }; + /// ``` + pub TRANSMUTE_PTR_TO_PTR, + complexity, + "transmutes from a pointer to a pointer / a reference to a reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes between collections whose + /// types have different ABI, size or alignment. + /// + /// **Why is this bad?** This is undefined behavior. + /// + /// **Known problems:** Currently, we cannot know whether a type is a + /// collection, so we just lint the ones that come with `std`. + /// + /// **Example:** + /// ```rust + /// // different size, therefore likely out-of-bounds memory access + /// // You absolutely do not want this in your code! + /// unsafe { + /// std::mem::transmute::<_, Vec>(vec![2_u16]) + /// }; + /// ``` + /// + /// You must always iterate, map and collect the values: + /// + /// ```rust + /// vec![2_u16].into_iter().map(u32::from).collect::>(); + /// ``` + pub UNSOUND_COLLECTION_TRANSMUTE, + correctness, + "transmute between collections of layout-incompatible types" +} +declare_lint_pass!(Transmute => [ + CROSSPOINTER_TRANSMUTE, + TRANSMUTE_PTR_TO_REF, + TRANSMUTE_PTR_TO_PTR, + USELESS_TRANSMUTE, + WRONG_TRANSMUTE, + TRANSMUTE_INT_TO_CHAR, + TRANSMUTE_BYTES_TO_STR, + TRANSMUTE_INT_TO_BOOL, + TRANSMUTE_INT_TO_FLOAT, + TRANSMUTE_FLOAT_TO_INT, + UNSOUND_COLLECTION_TRANSMUTE, +]); + +// used to check for UNSOUND_COLLECTION_TRANSMUTE +static COLLECTIONS: &[&[&str]] = &[ + &paths::VEC, + &paths::VEC_DEQUE, + &paths::BINARY_HEAP, + &paths::BTREESET, + &paths::BTREEMAP, + &paths::HASHSET, + &paths::HASHMAP, +]; +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute { + #[allow(clippy::similar_names, clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref path_expr, ref args) = e.kind; + if let ExprKind::Path(ref qpath) = path_expr.kind; + if let Some(def_id) = cx.tables.qpath_res(qpath, path_expr.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::TRANSMUTE); + then { + let from_ty = cx.tables.expr_ty(&args[0]); + let to_ty = cx.tables.expr_ty(e); + + match (&from_ty.kind, &to_ty.kind) { + _ if from_ty == to_ty => span_lint( + cx, + USELESS_TRANSMUTE, + e.span, + &format!("transmute from a type (`{}`) to itself", from_ty), + ), + (&ty::Ref(_, rty, rty_mutbl), &ty::RawPtr(ptr_ty)) => span_lint_and_then( + cx, + USELESS_TRANSMUTE, + e.span, + "transmute from a reference to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let rty_and_mut = ty::TypeAndMut { + ty: rty, + mutbl: rty_mutbl, + }; + + let sugg = if ptr_ty == rty_and_mut { + arg.as_ty(to_ty) + } else { + arg.as_ty(cx.tcx.mk_ptr(rty_and_mut)).as_ty(to_ty) + }; + + diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); + } + }, + ), + (&ty::Int(_), &ty::RawPtr(_)) | (&ty::Uint(_), &ty::RawPtr(_)) => span_lint_and_then( + cx, + USELESS_TRANSMUTE, + e.span, + "transmute from an integer to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + diag.span_suggestion( + e.span, + "try", + arg.as_ty(&to_ty.to_string()).to_string(), + Applicability::Unspecified, + ); + } + }, + ), + (&ty::Float(_), &ty::Ref(..)) + | (&ty::Float(_), &ty::RawPtr(_)) + | (&ty::Char, &ty::Ref(..)) + | (&ty::Char, &ty::RawPtr(_)) => span_lint( + cx, + WRONG_TRANSMUTE, + e.span, + &format!("transmute from a `{}` to a pointer", from_ty), + ), + (&ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => span_lint( + cx, + CROSSPOINTER_TRANSMUTE, + e.span, + &format!( + "transmute from a type (`{}`) to the type that it points to (`{}`)", + from_ty, to_ty + ), + ), + (_, &ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => span_lint( + cx, + CROSSPOINTER_TRANSMUTE, + e.span, + &format!( + "transmute from a type (`{}`) to a pointer to that type (`{}`)", + from_ty, to_ty + ), + ), + (&ty::RawPtr(from_pty), &ty::Ref(_, to_ref_ty, mutbl)) => span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_REF, + e.span, + &format!( + "transmute from a pointer type (`{}`) to a reference type \ + (`{}`)", + from_ty, to_ty + ), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let (deref, cast) = if mutbl == Mutability::Mut { + ("&mut *", "*mut") + } else { + ("&*", "*const") + }; + + let arg = if from_pty.ty == to_ref_ty { + arg + } else { + arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty))) + }; + + diag.span_suggestion( + e.span, + "try", + sugg::make_unop(deref, arg).to_string(), + Applicability::Unspecified, + ); + }, + ), + (&ty::Int(ast::IntTy::I32), &ty::Char) | (&ty::Uint(ast::UintTy::U32), &ty::Char) => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_CHAR, + e.span, + &format!("transmute from a `{}` to a `char`", from_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = if let ty::Int(_) = from_ty.kind { + arg.as_ty(ast::UintTy::U32.name_str()) + } else { + arg + }; + diag.span_suggestion( + e.span, + "consider using", + format!("std::char::from_u32({}).unwrap()", arg.to_string()), + Applicability::Unspecified, + ); + }, + ) + }, + (&ty::Ref(_, ty_from, from_mutbl), &ty::Ref(_, ty_to, to_mutbl)) => { + if_chain! { + if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind, &ty_to.kind); + if let ty::Uint(ast::UintTy::U8) = slice_ty.kind; + if from_mutbl == to_mutbl; + then { + let postfix = if from_mutbl == Mutability::Mut { + "_mut" + } else { + "" + }; + + span_lint_and_sugg( + cx, + TRANSMUTE_BYTES_TO_STR, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + "consider using", + format!( + "std::str::from_utf8{}({}).unwrap()", + postfix, + snippet(cx, args[0].span, ".."), + ), + Applicability::Unspecified, + ); + } else { + if cx.tcx.erase_regions(&from_ty) != cx.tcx.erase_regions(&to_ty) { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_PTR, + e.span, + "transmute from a reference to a reference", + |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let ty_from_and_mut = ty::TypeAndMut { + ty: ty_from, + mutbl: from_mutbl + }; + let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: to_mutbl }; + let sugg_paren = arg + .as_ty(cx.tcx.mk_ptr(ty_from_and_mut)) + .as_ty(cx.tcx.mk_ptr(ty_to_and_mut)); + let sugg = if to_mutbl == Mutability::Mut { + sugg_paren.mut_addr_deref() + } else { + sugg_paren.addr_deref() + }; + diag.span_suggestion( + e.span, + "try", + sugg.to_string(), + Applicability::Unspecified, + ); + }, + ) + } + } + } + }, + (&ty::RawPtr(_), &ty::RawPtr(to_ty)) => span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_PTR, + e.span, + "transmute from a pointer to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let sugg = arg.as_ty(cx.tcx.mk_ptr(to_ty)); + diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); + } + }, + ), + (&ty::Int(ast::IntTy::I8), &ty::Bool) | (&ty::Uint(ast::UintTy::U8), &ty::Bool) => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_BOOL, + e.span, + &format!("transmute from a `{}` to a `bool`", from_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let zero = sugg::Sugg::NonParen(Cow::from("0")); + diag.span_suggestion( + e.span, + "consider using", + sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string(), + Applicability::Unspecified, + ); + }, + ) + }, + (&ty::Int(_), &ty::Float(_)) | (&ty::Uint(_), &ty::Float(_)) => span_lint_and_then( + cx, + TRANSMUTE_INT_TO_FLOAT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = if let ty::Int(int_ty) = from_ty.kind { + arg.as_ty(format!( + "u{}", + int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string()) + )) + } else { + arg + }; + diag.span_suggestion( + e.span, + "consider using", + format!("{}::from_bits({})", to_ty, arg.to_string()), + Applicability::Unspecified, + ); + }, + ), + (&ty::Float(float_ty), &ty::Int(_)) | (&ty::Float(float_ty), &ty::Uint(_)) => span_lint_and_then( + cx, + TRANSMUTE_FLOAT_TO_INT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |diag| { + let mut expr = &args[0]; + let mut arg = sugg::Sugg::hir(cx, expr, ".."); + + if let ExprKind::Unary(UnOp::UnNeg, inner_expr) = &expr.kind { + expr = &inner_expr; + } + + if_chain! { + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + let op = format!("{}{}", arg, float_ty.name_str()).into(); + if let ExprKind::Lit(lit) = &expr.kind; + if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; + then { + match arg { + sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), + _ => arg = sugg::Sugg::NonParen(op) + } + } + } + + arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into()); + + // cast the result of `to_bits` if `to_ty` is signed + arg = if let ty::Int(int_ty) = to_ty.kind { + arg.as_ty(int_ty.name_str().to_string()) + } else { + arg + }; + + diag.span_suggestion( + e.span, + "consider using", + arg.to_string(), + Applicability::Unspecified, + ); + }, + ), + (&ty::Adt(ref from_adt, ref from_substs), &ty::Adt(ref to_adt, ref to_substs)) => { + if from_adt.did != to_adt.did || + !COLLECTIONS.iter().any(|path| match_def_path(cx, to_adt.did, path)) { + return; + } + if from_substs.types().zip(to_substs.types()) + .any(|(from_ty, to_ty)| is_layout_incompatible(cx, from_ty, to_ty)) { + span_lint( + cx, + UNSOUND_COLLECTION_TRANSMUTE, + e.span, + &format!( + "transmute from `{}` to `{}` with mismatched layout is unsound", + from_ty, + to_ty + ) + ); + } + }, + _ => return, + } + } + } + } +} + +/// Gets the snippet of `Bar` in `…::transmute`. If that snippet is +/// not available , use +/// the type's `ToString` implementation. In weird cases it could lead to types +/// with invalid `'_` +/// lifetime, but it should be rare. +fn get_type_snippet(cx: &LateContext<'_, '_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String { + let seg = last_path_segment(path); + if_chain! { + if let Some(ref params) = seg.args; + if !params.parenthesized; + if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).nth(1); + if let TyKind::Rptr(_, ref to_ty) = to_ty.kind; + then { + return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string(); + } + } + + to_ref_ty.to_string() +} + +// check if the component types of the transmuted collection and the result have different ABI, +// size or alignment +fn is_layout_incompatible<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { + let empty_param_env = ty::ParamEnv::empty(); + // check if `from` and `to` are normalizable to avoid ICE (#4968) + if !(is_normalizable(cx, empty_param_env, from) && is_normalizable(cx, empty_param_env, to)) { + return false; + } + let from_ty_layout = cx.tcx.layout_of(empty_param_env.and(from)); + let to_ty_layout = cx.tcx.layout_of(empty_param_env.and(to)); + if let (Ok(from_layout), Ok(to_layout)) = (from_ty_layout, to_ty_layout) { + from_layout.size != to_layout.size || from_layout.align != to_layout.align || from_layout.abi != to_layout.abi + } else { + // no idea about layout, so don't lint + false + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmuting_null.rs b/src/tools/clippy/clippy_lints/src/transmuting_null.rs new file mode 100644 index 0000000000000..1d0332c580500 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmuting_null.rs @@ -0,0 +1,89 @@ +use crate::consts::{constant_context, Constant}; +use crate::utils::{match_qpath, paths, span_lint}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for transmute calls which would receive a null pointer. + /// + /// **Why is this bad?** Transmuting a null pointer is undefined behavior. + /// + /// **Known problems:** Not all cases can be detected at the moment of this writing. + /// For example, variables which hold a null pointer and are then fed to a `transmute` + /// call, aren't detectable yet. + /// + /// **Example:** + /// ```rust + /// let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) }; + /// ``` + pub TRANSMUTING_NULL, + correctness, + "transmutes from a null pointer to a reference, which is undefined behavior" +} + +declare_lint_pass!(TransmutingNull => [TRANSMUTING_NULL]); + +const LINT_MSG: &str = "transmuting a known null pointer into a reference."; + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TransmutingNull { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if_chain! { + if let ExprKind::Call(ref func, ref args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &paths::STD_MEM_TRANSMUTE); + if args.len() == 1; + + then { + + // Catching transmute over constants that resolve to `null`. + let mut const_eval_context = constant_context(cx, cx.tables); + if_chain! { + if let ExprKind::Path(ref _qpath) = args[0].kind; + let x = const_eval_context.expr(&args[0]); + if let Some(constant) = x; + if let Constant::RawPtr(0) = constant; + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) + } + } + + // Catching: + // `std::mem::transmute(0 as *const i32)` + if_chain! { + if let ExprKind::Cast(ref inner_expr, ref _cast_ty) = args[0].kind; + if let ExprKind::Lit(ref lit) = inner_expr.kind; + if let LitKind::Int(0, _) = lit.node; + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) + } + } + + // Catching: + // `std::mem::transmute(std::ptr::null::())` + if_chain! { + if let ExprKind::Call(ref func1, ref args1) = args[0].kind; + if let ExprKind::Path(ref path1) = func1.kind; + if match_qpath(path1, &paths::STD_PTR_NULL); + if args1.is_empty(); + then { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG) + } + } + + // FIXME: + // Also catch transmutations of variables which are known nulls. + // To do this, MIR const propagation seems to be the better tool. + // Whenever MIR const prop routines are more developed, this will + // become available. As of this writing (25/03/19) it is not yet. + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/trivially_copy_pass_by_ref.rs b/src/tools/clippy/clippy_lints/src/trivially_copy_pass_by_ref.rs new file mode 100644 index 0000000000000..2c101220c5d68 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/trivially_copy_pass_by_ref.rs @@ -0,0 +1,178 @@ +use std::cmp; + +use crate::utils::{is_copy, is_self_ty, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, FnDecl, HirId, ItemKind, MutTy, Mutability, Node}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::config::Config as SessionConfig; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; +use rustc_target::abi::LayoutOf; +use rustc_target::spec::abi::Abi; + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by reference, where + /// the argument type is `Copy` and small enough to be more efficient to always + /// pass by value. + /// + /// **Why is this bad?** In many calling conventions instances of structs will + /// be passed through registers if they fit into two or less general purpose + /// registers. + /// + /// **Known problems:** This lint is target register size dependent, it is + /// limited to 32-bit to try and reduce portability problems between 32 and + /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit + /// will be different. + /// + /// The configuration option `trivial_copy_size_limit` can be set to override + /// this limit for a project. + /// + /// This lint attempts to allow passing arguments by reference if a reference + /// to that argument is returned. This is implemented by comparing the lifetime + /// of the argument and return value for equality. However, this can cause + /// false positives in cases involving multiple lifetimes that are bounded by + /// each other. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// fn foo(v: &u32) {} + /// ``` + /// + /// ```rust + /// // Better + /// fn foo(v: u32) {} + /// ``` + pub TRIVIALLY_COPY_PASS_BY_REF, + pedantic, + "functions taking small copyable arguments by reference" +} + +#[derive(Copy, Clone)] +pub struct TriviallyCopyPassByRef { + limit: u64, +} + +impl<'a, 'tcx> TriviallyCopyPassByRef { + pub fn new(limit: Option, target: &SessionConfig) -> Self { + let limit = limit.unwrap_or_else(|| { + let bit_width = u64::from(target.ptr_width); + // Cap the calculated bit width at 32-bits to reduce + // portability problems between 32 and 64-bit targets + let bit_width = cmp::min(bit_width, 32); + #[allow(clippy::integer_division)] + let byte_width = bit_width / 8; + // Use a limit of 2 times the register byte width + byte_width * 2 + }); + Self { limit } + } + + fn check_poly_fn(&mut self, cx: &LateContext<'_, 'tcx>, hir_id: HirId, decl: &FnDecl<'_>, span: Option) { + let fn_def_id = cx.tcx.hir().local_def_id(hir_id); + + let fn_sig = cx.tcx.fn_sig(fn_def_id); + let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig); + + // Use lifetimes to determine if we're returning a reference to the + // argument. In that case we can't switch to pass-by-value as the + // argument will not live long enough. + let output_lts = match fn_sig.output().kind { + ty::Ref(output_lt, _, _) => vec![output_lt], + ty::Adt(_, substs) => substs.regions().collect(), + _ => vec![], + }; + + for (input, &ty) in decl.inputs.iter().zip(fn_sig.inputs()) { + // All spans generated from a proc-macro invocation are the same... + match span { + Some(s) if s == input.span => return, + _ => (), + } + + if_chain! { + if let ty::Ref(input_lt, ty, Mutability::Not) = ty.kind; + if !output_lts.contains(&input_lt); + if is_copy(cx, ty); + if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); + if size <= self.limit; + if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind; + then { + let value_type = if is_self_ty(decl_ty) { + "self".into() + } else { + snippet(cx, decl_ty.span, "_").into() + }; + span_lint_and_sugg( + cx, + TRIVIALLY_COPY_PASS_BY_REF, + input.span, + &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.limit), + "consider passing by value instead", + value_type, + Applicability::Unspecified, + ); + } + } + } + } +} + +impl_lint_pass!(TriviallyCopyPassByRef => [TRIVIALLY_COPY_PASS_BY_REF]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TriviallyCopyPassByRef { + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'_>) { + if item.span.from_expansion() { + return; + } + + if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind { + self.check_poly_fn(cx, item.hir_id, &*method_sig.decl, None); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + _body: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if span.from_expansion() { + return; + } + + match kind { + FnKind::ItemFn(.., header, _, attrs) => { + if header.abi != Abi::Rust { + return; + } + for a in attrs { + if a.meta_item_list().is_some() && a.check_name(sym!(proc_macro_derive)) { + return; + } + } + }, + FnKind::Method(..) => (), + _ => return, + } + + // Exclude non-inherent impls + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + if matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. } | + ItemKind::Trait(..)) + { + return; + } + } + + self.check_poly_fn(cx, hir_id, decl, Some(span)); + } +} diff --git a/src/tools/clippy/clippy_lints/src/try_err.rs b/src/tools/clippy/clippy_lints/src/try_err.rs new file mode 100644 index 0000000000000..7018fa6804ba7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/try_err.rs @@ -0,0 +1,122 @@ +use crate::utils::{match_qpath, paths, snippet, snippet_with_macro_callsite, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::Ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for usages of `Err(x)?`. + /// + /// **Why is this bad?** The `?` operator is designed to allow calls that + /// can fail to be easily chained. For example, `foo()?.bar()` or + /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will + /// always return), it is more clear to write `return Err(x)`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo(fail: bool) -> Result { + /// if fail { + /// Err("failed")?; + /// } + /// Ok(0) + /// } + /// ``` + /// Could be written: + /// + /// ```rust + /// fn foo(fail: bool) -> Result { + /// if fail { + /// return Err("failed".into()); + /// } + /// Ok(0) + /// } + /// ``` + pub TRY_ERR, + style, + "return errors explicitly rather than hiding them behind a `?`" +} + +declare_lint_pass!(TryErr => [TRY_ERR]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TryErr { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + // Looks for a structure like this: + // match ::std::ops::Try::into_result(Err(5)) { + // ::std::result::Result::Err(err) => + // #[allow(unreachable_code)] + // return ::std::ops::Try::from_error(::std::convert::From::from(err)), + // ::std::result::Result::Ok(val) => + // #[allow(unreachable_code)] + // val, + // }; + if_chain! { + if !in_external_macro(cx.tcx.sess, expr.span); + if let ExprKind::Match(ref match_arg, _, MatchSource::TryDesugar) = expr.kind; + if let ExprKind::Call(ref match_fun, ref try_args) = match_arg.kind; + if let ExprKind::Path(ref match_fun_path) = match_fun.kind; + if match_qpath(match_fun_path, &paths::TRY_INTO_RESULT); + if let Some(ref try_arg) = try_args.get(0); + if let ExprKind::Call(ref err_fun, ref err_args) = try_arg.kind; + if let Some(ref err_arg) = err_args.get(0); + if let ExprKind::Path(ref err_fun_path) = err_fun.kind; + if match_qpath(err_fun_path, &paths::RESULT_ERR); + if let Some(return_type) = find_err_return_type(cx, &expr.kind); + + then { + let err_type = cx.tables.expr_ty(err_arg); + let origin_snippet = if err_arg.span.from_expansion() { + snippet_with_macro_callsite(cx, err_arg.span, "_") + } else { + snippet(cx, err_arg.span, "_") + }; + let suggestion = if err_type == return_type { + format!("return Err({})", origin_snippet) + } else { + format!("return Err({}.into())", origin_snippet) + }; + + span_lint_and_sugg( + cx, + TRY_ERR, + expr.span, + "returning an `Err(_)` with the `?` operator", + "try this", + suggestion, + Applicability::MachineApplicable + ); + } + } + } +} + +// In order to determine whether to suggest `.into()` or not, we need to find the error type the +// function returns. To do that, we look for the From::from call (see tree above), and capture +// its output type. +fn find_err_return_type<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx ExprKind<'_>) -> Option> { + if let ExprKind::Match(_, ref arms, MatchSource::TryDesugar) = expr { + arms.iter().find_map(|ty| find_err_return_type_arm(cx, ty)) + } else { + None + } +} + +// Check for From::from in one of the match arms. +fn find_err_return_type_arm<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arm: &'tcx Arm<'_>) -> Option> { + if_chain! { + if let ExprKind::Ret(Some(ref err_ret)) = arm.body.kind; + if let ExprKind::Call(ref from_error_path, ref from_error_args) = err_ret.kind; + if let ExprKind::Path(ref from_error_fn) = from_error_path.kind; + if match_qpath(from_error_fn, &paths::TRY_FROM_ERROR); + if let Some(from_error_arg) = from_error_args.get(0); + then { + Some(cx.tables.expr_ty(from_error_arg)) + } else { + None + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/types.rs b/src/tools/clippy/clippy_lints/src/types.rs new file mode 100644 index 0000000000000..6d49f50d550e8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/types.rs @@ -0,0 +1,2562 @@ +#![allow(rustc::default_hash_types)] + +use std::borrow::Cow; +use std::cmp::Ordering; +use std::collections::BTreeMap; + +use if_chain::if_chain; +use rustc_ast::ast::{FloatTy, IntTy, LitFloatType, LitIntType, LitKind, UintTy}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{ + BinOpKind, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, + ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, QPath, Stmt, StmtKind, TraitFn, + TraitItem, TraitItemKind, TyKind, UnOp, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, InferTy, Ty, TyCtxt, TypeckTables}; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; +use rustc_target::abi::LayoutOf; +use rustc_target::spec::abi::Abi; +use rustc_typeck::hir_ty_to_ty; + +use crate::consts::{constant, Constant}; +use crate::utils::paths; +use crate::utils::{ + clip, comparisons, differing_macro_contexts, higher, in_constant, int_bits, is_type_diagnostic_item, + last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal::NumericLiteral, + qpath_res, same_tys, sext, snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite, + span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext, +}; + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Box>` anywhere in the code. + /// + /// **Why is this bad?** `Vec` already keeps its contents in a separate area on + /// the heap. So if you `Box` it, you just add another level of indirection + /// without any benefit whatsoever. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// struct X { + /// values: Box>, + /// } + /// ``` + /// + /// Better: + /// + /// ```rust,ignore + /// struct X { + /// values: Vec, + /// } + /// ``` + pub BOX_VEC, + perf, + "usage of `Box>`, vector elements are already on the heap" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Vec>` where T: Sized anywhere in the code. + /// + /// **Why is this bad?** `Vec` already keeps its contents in a separate area on + /// the heap. So if you `Box` its contents, you just add another level of indirection. + /// + /// **Known problems:** Vec> makes sense if T is a large type (see #3530, + /// 1st comment). + /// + /// **Example:** + /// ```rust + /// struct X { + /// values: Vec>, + /// } + /// ``` + /// + /// Better: + /// + /// ```rust + /// struct X { + /// values: Vec, + /// } + /// ``` + pub VEC_BOX, + complexity, + "usage of `Vec>` where T: Sized, vector elements are already on the heap" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Option>` in function signatures and type + /// definitions + /// + /// **Why is this bad?** `Option<_>` represents an optional value. `Option>` + /// represents an optional optional value which is logically the same thing as an optional + /// value but has an unneeded extra level of wrapping. + /// + /// If you have a case where `Some(Some(_))`, `Some(None)` and `None` are distinct cases, + /// consider a custom `enum` instead, with clear names for each case. + /// + /// **Known problems:** None. + /// + /// **Example** + /// ```rust + /// fn get_data() -> Option> { + /// None + /// } + /// ``` + /// + /// Better: + /// + /// ```rust + /// pub enum Contents { + /// Data(Vec), // Was Some(Some(Vec)) + /// NotYetFetched, // Was Some(None) + /// None, // Was None + /// } + /// + /// fn get_data() -> Contents { + /// Contents::None + /// } + /// ``` + pub OPTION_OPTION, + pedantic, + "usage of `Option>`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of any `LinkedList`, suggesting to use a + /// `Vec` or a `VecDeque` (formerly called `RingBuf`). + /// + /// **Why is this bad?** Gankro says: + /// + /// > The TL;DR of `LinkedList` is that it's built on a massive amount of + /// pointers and indirection. + /// > It wastes memory, it has terrible cache locality, and is all-around slow. + /// `RingBuf`, while + /// > "only" amortized for push/pop, should be faster in the general case for + /// almost every possible + /// > workload, and isn't even amortized at all if you can predict the capacity + /// you need. + /// > + /// > `LinkedList`s are only really good if you're doing a lot of merging or + /// splitting of lists. + /// > This is because they can just mangle some pointers instead of actually + /// copying the data. Even + /// > if you're doing a lot of insertion in the middle of the list, `RingBuf` + /// can still be better + /// > because of how expensive it is to seek to the middle of a `LinkedList`. + /// + /// **Known problems:** False positives – the instances where using a + /// `LinkedList` makes sense are few and far between, but they can still happen. + /// + /// **Example:** + /// ```rust + /// # use std::collections::LinkedList; + /// let x: LinkedList = LinkedList::new(); + /// ``` + pub LINKEDLIST, + pedantic, + "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `&Box` anywhere in the code. + /// + /// **Why is this bad?** Any `&Box` can also be a `&T`, which is more + /// general. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// fn foo(bar: &Box) { ... } + /// ``` + /// + /// Better: + /// + /// ```rust,ignore + /// fn foo(bar: &T) { ... } + /// ``` + pub BORROWED_BOX, + complexity, + "a borrow of a boxed type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of redundant allocations anywhere in the code. + /// + /// **Why is this bad?** Expressions such as `Rc<&T>`, `Rc>`, `Rc>`, `Box<&T>` + /// add an unnecessary level of indirection. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::rc::Rc; + /// fn foo(bar: Rc<&usize>) {} + /// ``` + /// + /// Better: + /// + /// ```rust + /// fn foo(bar: &usize) {} + /// ``` + pub REDUNDANT_ALLOCATION, + perf, + "redundant allocation" +} + +pub struct Types { + vec_box_size_threshold: u64, +} + +impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Types { + fn check_fn( + &mut self, + cx: &LateContext<'_, '_>, + _: FnKind<'_>, + decl: &FnDecl<'_>, + _: &Body<'_>, + _: Span, + id: HirId, + ) { + // Skip trait implementations; see issue #605. + if let Some(hir::Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(id)) { + if let ItemKind::Impl { of_trait: Some(_), .. } = item.kind { + return; + } + } + + self.check_fn_decl(cx, decl); + } + + fn check_struct_field(&mut self, cx: &LateContext<'_, '_>, field: &hir::StructField<'_>) { + self.check_ty(cx, &field.ty, false); + } + + fn check_trait_item(&mut self, cx: &LateContext<'_, '_>, item: &TraitItem<'_>) { + match item.kind { + TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_ty(cx, ty, false), + TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, &sig.decl), + _ => (), + } + } + + fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &Local<'_>) { + if let Some(ref ty) = local.ty { + self.check_ty(cx, ty, true); + } + } +} + +/// Checks if `qpath` has last segment with type parameter matching `path` +fn match_type_parameter(cx: &LateContext<'_, '_>, qpath: &QPath<'_>, path: &[&str]) -> Option { + let last = last_path_segment(qpath); + if_chain! { + if let Some(ref params) = last.args; + if !params.parenthesized; + if let Some(ty) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + if let TyKind::Path(ref qpath) = ty.kind; + if let Some(did) = qpath_res(cx, qpath, ty.hir_id).opt_def_id(); + if match_def_path(cx, did, path); + then { + return Some(ty.span); + } + } + None +} + +fn match_borrows_parameter(_cx: &LateContext<'_, '_>, qpath: &QPath<'_>) -> Option { + let last = last_path_segment(qpath); + if_chain! { + if let Some(ref params) = last.args; + if !params.parenthesized; + if let Some(ty) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + if let TyKind::Rptr(..) = ty.kind; + then { + return Some(ty.span); + } + } + None +} + +impl Types { + pub fn new(vec_box_size_threshold: u64) -> Self { + Self { vec_box_size_threshold } + } + + fn check_fn_decl(&mut self, cx: &LateContext<'_, '_>, decl: &FnDecl<'_>) { + for input in decl.inputs { + self.check_ty(cx, input, false); + } + + if let FnRetTy::Return(ref ty) = decl.output { + self.check_ty(cx, ty, false); + } + } + + /// Recursively check for `TypePass` lints in the given type. Stop at the first + /// lint found. + /// + /// The parameter `is_local` distinguishes the context of the type; types from + /// local bindings should only be checked for the `BORROWED_BOX` lint. + #[allow(clippy::too_many_lines)] + fn check_ty(&mut self, cx: &LateContext<'_, '_>, hir_ty: &hir::Ty<'_>, is_local: bool) { + if hir_ty.span.from_expansion() { + return; + } + match hir_ty.kind { + TyKind::Path(ref qpath) if !is_local => { + let hir_id = hir_ty.hir_id; + let res = qpath_res(cx, qpath, hir_id); + if let Some(def_id) = res.opt_def_id() { + if Some(def_id) == cx.tcx.lang_items().owned_box() { + if let Some(span) = match_borrows_parameter(cx, qpath) { + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Box<&T>`", + "try", + snippet(cx, span, "..").to_string(), + Applicability::MachineApplicable, + ); + return; // don't recurse into the type + } + if match_type_parameter(cx, qpath, &paths::VEC).is_some() { + span_lint_and_help( + cx, + BOX_VEC, + hir_ty.span, + "you seem to be trying to use `Box>`. Consider using just `Vec`", + None, + "`Vec` is already on the heap, `Box>` makes an extra allocation.", + ); + return; // don't recurse into the type + } + } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + if let Some(span) = match_type_parameter(cx, qpath, &paths::RC) { + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc>`", + "try", + snippet(cx, span, "..").to_string(), + Applicability::MachineApplicable, + ); + return; // don't recurse into the type + } + if let Some(span) = match_type_parameter(cx, qpath, &paths::BOX) { + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc>`", + "try", + snippet(cx, span, "..").to_string(), + Applicability::MachineApplicable, + ); + return; // don't recurse into the type + } + if let Some(span) = match_borrows_parameter(cx, qpath) { + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc<&T>`", + "try", + snippet(cx, span, "..").to_string(), + Applicability::MachineApplicable, + ); + return; // don't recurse into the type + } + } else if cx.tcx.is_diagnostic_item(sym!(vec_type), def_id) { + if_chain! { + // Get the _ part of Vec<_> + if let Some(ref last) = last_path_segment(qpath).args; + if let Some(ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + // ty is now _ at this point + if let TyKind::Path(ref ty_qpath) = ty.kind; + let res = qpath_res(cx, ty_qpath, ty.hir_id); + if let Some(def_id) = res.opt_def_id(); + if Some(def_id) == cx.tcx.lang_items().owned_box(); + // At this point, we know ty is Box, now get T + if let Some(ref last) = last_path_segment(ty_qpath).args; + if let Some(boxed_ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + let ty_ty = hir_ty_to_ty(cx.tcx, boxed_ty); + if ty_ty.is_sized(cx.tcx.at(ty.span), cx.param_env); + if let Ok(ty_ty_size) = cx.layout_of(ty_ty).map(|l| l.size.bytes()); + if ty_ty_size <= self.vec_box_size_threshold; + then { + span_lint_and_sugg( + cx, + VEC_BOX, + hir_ty.span, + "`Vec` is already on the heap, the boxing is unnecessary.", + "try", + format!("Vec<{}>", ty_ty), + Applicability::MachineApplicable, + ); + return; // don't recurse into the type + } + } + } else if cx.tcx.is_diagnostic_item(sym!(option_type), def_id) { + if match_type_parameter(cx, qpath, &paths::OPTION).is_some() { + span_lint( + cx, + OPTION_OPTION, + hir_ty.span, + "consider using `Option` instead of `Option>` or a custom \ + enum if you need to distinguish all 3 cases", + ); + return; // don't recurse into the type + } + } else if match_def_path(cx, def_id, &paths::LINKED_LIST) { + span_lint_and_help( + cx, + LINKEDLIST, + hir_ty.span, + "I see you're using a LinkedList! Perhaps you meant some other data structure?", + None, + "a `VecDeque` might work", + ); + return; // don't recurse into the type + } + } + match *qpath { + QPath::Resolved(Some(ref ty), ref p) => { + self.check_ty(cx, ty, is_local); + for ty in p.segments.iter().flat_map(|seg| { + seg.args + .as_ref() + .map_or_else(|| [].iter(), |params| params.args.iter()) + .filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + }) { + self.check_ty(cx, ty, is_local); + } + }, + QPath::Resolved(None, ref p) => { + for ty in p.segments.iter().flat_map(|seg| { + seg.args + .as_ref() + .map_or_else(|| [].iter(), |params| params.args.iter()) + .filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + }) { + self.check_ty(cx, ty, is_local); + } + }, + QPath::TypeRelative(ref ty, ref seg) => { + self.check_ty(cx, ty, is_local); + if let Some(ref params) = seg.args { + for ty in params.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) { + self.check_ty(cx, ty, is_local); + } + } + }, + } + }, + TyKind::Rptr(ref lt, ref mut_ty) => self.check_ty_rptr(cx, hir_ty, is_local, lt, mut_ty), + // recurse + TyKind::Slice(ref ty) | TyKind::Array(ref ty, _) | TyKind::Ptr(MutTy { ref ty, .. }) => { + self.check_ty(cx, ty, is_local) + }, + TyKind::Tup(tys) => { + for ty in tys { + self.check_ty(cx, ty, is_local); + } + }, + _ => {}, + } + } + + fn check_ty_rptr( + &mut self, + cx: &LateContext<'_, '_>, + hir_ty: &hir::Ty<'_>, + is_local: bool, + lt: &Lifetime, + mut_ty: &MutTy<'_>, + ) { + match mut_ty.ty.kind { + TyKind::Path(ref qpath) => { + let hir_id = mut_ty.ty.hir_id; + let def = qpath_res(cx, qpath, hir_id); + if_chain! { + if let Some(def_id) = def.opt_def_id(); + if Some(def_id) == cx.tcx.lang_items().owned_box(); + if let QPath::Resolved(None, ref path) = *qpath; + if let [ref bx] = *path.segments; + if let Some(ref params) = bx.args; + if !params.parenthesized; + if let Some(inner) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + then { + if is_any_trait(inner) { + // Ignore `Box` types; see issue #1884 for details. + return; + } + + let ltopt = if lt.is_elided() { + String::new() + } else { + format!("{} ", lt.name.ident().as_str()) + }; + + if mut_ty.mutbl == Mutability::Mut { + // Ignore `&mut Box` types; see issue #2907 for + // details. + return; + } + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + BORROWED_BOX, + hir_ty.span, + "you seem to be trying to use `&Box`. Consider using just `&T`", + "try", + format!( + "&{}{}", + ltopt, + &snippet_with_applicability(cx, inner.span, "..", &mut applicability) + ), + Applicability::Unspecified, + ); + return; // don't recurse into the type + } + }; + self.check_ty(cx, &mut_ty.ty, is_local); + }, + _ => self.check_ty(cx, &mut_ty.ty, is_local), + } + } +} + +// Returns true if given type is `Any` trait. +fn is_any_trait(t: &hir::Ty<'_>) -> bool { + if_chain! { + if let TyKind::TraitObject(ref traits, _) = t.kind; + if !traits.is_empty(); + // Only Send/Sync can be used as additional traits, so it is enough to + // check only the first trait. + if match_path(&traits[0].trait_ref.path, &paths::ANY_TRAIT); + then { + return true; + } + } + + false +} + +declare_clippy_lint! { + /// **What it does:** Checks for binding a unit value. + /// + /// **Why is this bad?** A unit value cannot usefully be used anywhere. So + /// binding one is kind of pointless. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = { + /// 1; + /// }; + /// ``` + pub LET_UNIT_VALUE, + pedantic, + "creating a `let` binding to a value of unit type, which usually can't be used afterwards" +} + +declare_lint_pass!(LetUnitValue => [LET_UNIT_VALUE]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetUnitValue { + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Local(ref local) = stmt.kind { + if is_unit(cx.tables.pat_ty(&local.pat)) { + if in_external_macro(cx.sess(), stmt.span) || local.pat.span.from_expansion() { + return; + } + if higher::is_from_for_desugar(local) { + return; + } + span_lint_and_then( + cx, + LET_UNIT_VALUE, + stmt.span, + "this let-binding has unit value", + |diag| { + if let Some(expr) = &local.init { + let snip = snippet_with_macro_callsite(cx, expr.span, "()"); + diag.span_suggestion( + stmt.span, + "omit the `let` binding", + format!("{};", snip), + Applicability::MachineApplicable, // snippet + ); + } + }, + ); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons to unit. This includes all binary + /// comparisons (like `==` and `<`) and asserts. + /// + /// **Why is this bad?** Unit is always equal to itself, and thus is just a + /// clumsily written constant. Mostly this happens when someone accidentally + /// adds semicolons at the end of the operands. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # fn foo() {}; + /// # fn bar() {}; + /// # fn baz() {}; + /// if { + /// foo(); + /// } == { + /// bar(); + /// } { + /// baz(); + /// } + /// ``` + /// is equal to + /// ```rust + /// # fn foo() {}; + /// # fn bar() {}; + /// # fn baz() {}; + /// { + /// foo(); + /// bar(); + /// baz(); + /// } + /// ``` + /// + /// For asserts: + /// ```rust + /// # fn foo() {}; + /// # fn bar() {}; + /// assert_eq!({ foo(); }, { bar(); }); + /// ``` + /// will always succeed + pub UNIT_CMP, + correctness, + "comparing unit values" +} + +declare_lint_pass!(UnitCmp => [UNIT_CMP]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitCmp { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) { + if expr.span.from_expansion() { + if let Some(callee) = expr.span.source_callee() { + if let ExpnKind::Macro(MacroKind::Bang, symbol) = callee.kind { + if let ExprKind::Binary(ref cmp, ref left, _) = expr.kind { + let op = cmp.node; + if op.is_comparison() && is_unit(cx.tables.expr_ty(left)) { + let result = match &*symbol.as_str() { + "assert_eq" | "debug_assert_eq" => "succeed", + "assert_ne" | "debug_assert_ne" => "fail", + _ => return, + }; + span_lint( + cx, + UNIT_CMP, + expr.span, + &format!( + "`{}` of unit values detected. This will always {}", + symbol.as_str(), + result + ), + ); + } + } + } + } + return; + } + if let ExprKind::Binary(ref cmp, ref left, _) = expr.kind { + let op = cmp.node; + if op.is_comparison() && is_unit(cx.tables.expr_ty(left)) { + let result = match op { + BinOpKind::Eq | BinOpKind::Le | BinOpKind::Ge => "true", + _ => "false", + }; + span_lint( + cx, + UNIT_CMP, + expr.span, + &format!( + "{}-comparison of unit values detected. This will always be {}", + op.as_str(), + result + ), + ); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for passing a unit value as an argument to a function without using a + /// unit literal (`()`). + /// + /// **Why is this bad?** This is likely the result of an accidental semicolon. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// foo({ + /// let a = bar(); + /// baz(a); + /// }) + /// ``` + pub UNIT_ARG, + complexity, + "passing unit to a function" +} + +declare_lint_pass!(UnitArg => [UNIT_ARG]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + // apparently stuff in the desugaring of `?` can trigger this + // so check for that here + // only the calls to `Try::from_error` is marked as desugared, + // so we need to check both the current Expr and its parent. + if is_questionmark_desugar_marked_call(expr) { + return; + } + if_chain! { + let map = &cx.tcx.hir(); + let opt_parent_node = map.find(map.get_parent_node(expr.hir_id)); + if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node; + if is_questionmark_desugar_marked_call(parent_expr); + then { + return; + } + } + + match expr.kind { + ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => { + for arg in args { + if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) { + if let ExprKind::Match(.., match_source) = &arg.kind { + if *match_source == MatchSource::TryDesugar { + continue; + } + } + + span_lint_and_sugg( + cx, + UNIT_ARG, + arg.span, + "passing a unit value to a function", + "if you intended to pass a unit value, use a unit literal instead", + "()".to_string(), + Applicability::MaybeIncorrect, + ); + } + } + }, + _ => (), + } + } +} + +fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool { + use rustc_span::hygiene::DesugaringKind; + if let ExprKind::Call(ref callee, _) = expr.kind { + callee.span.is_desugaring(DesugaringKind::QuestionMark) + } else { + false + } +} + +fn is_unit(ty: Ty<'_>) -> bool { + match ty.kind { + ty::Tuple(slice) if slice.is_empty() => true, + _ => false, + } +} + +fn is_unit_literal(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Tup(ref slice) if slice.is_empty() => true, + _ => false, + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from any numerical to a float type where + /// the receiving type cannot store all values from the original type without + /// rounding errors. This possible rounding is to be expected, so this lint is + /// `Allow` by default. + /// + /// Basically, this warns on casting any integer with 32 or more bits to `f32` + /// or any 64-bit integer to `f64`. + /// + /// **Why is this bad?** It's not bad at all. But in some applications it can be + /// helpful to know where precision loss can take place. This lint can help find + /// those places in the code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = u64::MAX; + /// x as f64; + /// ``` + pub CAST_PRECISION_LOSS, + pedantic, + "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from a signed to an unsigned numerical + /// type. In this case, negative values wrap around to large positive values, + /// which can be quite surprising in practice. However, as the cast works as + /// defined, this lint is `Allow` by default. + /// + /// **Why is this bad?** Possibly surprising results. You can activate this lint + /// as a one-time check to see where numerical wrapping can arise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let y: i8 = -1; + /// y as u128; // will return 18446744073709551615 + /// ``` + pub CAST_SIGN_LOSS, + pedantic, + "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts between numerical types that may + /// truncate large values. This is expected behavior, so the cast is `Allow` by + /// default. + /// + /// **Why is this bad?** In some problem domains, it is good practice to avoid + /// truncation. This lint can be activated to help assess where additional + /// checks could be beneficial. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn as_u8(x: u64) -> u8 { + /// x as u8 + /// } + /// ``` + pub CAST_POSSIBLE_TRUNCATION, + pedantic, + "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from an unsigned type to a signed type of + /// the same size. Performing such a cast is a 'no-op' for the compiler, + /// i.e., nothing is changed at the bit level, and the binary representation of + /// the value is reinterpreted. This can cause wrapping if the value is too big + /// for the target signed type. However, the cast works as defined, so this lint + /// is `Allow` by default. + /// + /// **Why is this bad?** While such a cast is not bad in itself, the results can + /// be surprising when this is not the intended behavior, as demonstrated by the + /// example below. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// u32::MAX as i32; // will yield a value of `-1` + /// ``` + pub CAST_POSSIBLE_WRAP, + pedantic, + "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts between numerical types that may + /// be replaced by safe conversion functions. + /// + /// **Why is this bad?** Rust's `as` keyword will perform many kinds of + /// conversions, including silently lossy conversions. Conversion functions such + /// as `i32::from` will only perform lossless conversions. Using the conversion + /// functions prevents conversions from turning into silent lossy conversions if + /// the types of the input expressions ever change, and make it easier for + /// people reading the code to know that the conversion is lossless. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn as_u64(x: u8) -> u64 { + /// x as u64 + /// } + /// ``` + /// + /// Using `::from` would look like this: + /// + /// ```rust + /// fn as_u64(x: u8) -> u64 { + /// u64::from(x) + /// } + /// ``` + pub CAST_LOSSLESS, + pedantic, + "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts to the same type. + /// + /// **Why is this bad?** It's just unnecessary. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let _ = 2i32 as i32; + /// ``` + pub UNNECESSARY_CAST, + complexity, + "cast to the same type, e.g., `x as i32` where `x: i32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from a less-strictly-aligned pointer to a + /// more-strictly-aligned pointer + /// + /// **Why is this bad?** Dereferencing the resulting pointer may be undefined + /// behavior. + /// + /// **Known problems:** Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar + /// on the resulting pointer is fine. + /// + /// **Example:** + /// ```rust + /// let _ = (&1u8 as *const u8) as *const u16; + /// let _ = (&mut 1u8 as *mut u8) as *mut u16; + /// ``` + pub CAST_PTR_ALIGNMENT, + correctness, + "cast from a pointer to a more-strictly-aligned pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of function pointers to something other than usize + /// + /// **Why is this bad?** + /// Casting a function pointer to anything other than usize/isize is not portable across + /// architectures, because you end up losing bits if the target type is too small or end up with a + /// bunch of extra bits that waste space and add more instructions to the final binary than + /// strictly necessary for the problem + /// + /// Casting to isize also doesn't make sense since there are no signed addresses. + /// + /// **Example** + /// + /// ```rust + /// // Bad + /// fn fun() -> i32 { 1 } + /// let a = fun as i64; + /// + /// // Good + /// fn fun2() -> i32 { 1 } + /// let a = fun2 as usize; + /// ``` + pub FN_TO_NUMERIC_CAST, + style, + "casting a function pointer to a numeric type other than usize" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of a function pointer to a numeric type not wide enough to + /// store address. + /// + /// **Why is this bad?** + /// Such a cast discards some bits of the function's address. If this is intended, it would be more + /// clearly expressed by casting to usize first, then casting the usize to the intended type (with + /// a comment) to perform the truncation. + /// + /// **Example** + /// + /// ```rust + /// // Bad + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let _ = fn1 as i32; + /// + /// // Better: Cast to usize first, then comment with the reason for the truncation + /// fn fn2() -> i16 { + /// 1 + /// }; + /// let fn_ptr = fn2 as usize; + /// let fn_ptr_truncated = fn_ptr as i32; + /// ``` + pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + style, + "casting a function pointer to a numeric type not wide enough to store the address" +} + +/// Returns the size in bits of an integral type. +/// Will return 0 if the type is not an int or uint variant +fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { + match typ.kind { + ty::Int(i) => match i { + IntTy::Isize => tcx.data_layout.pointer_size.bits(), + IntTy::I8 => 8, + IntTy::I16 => 16, + IntTy::I32 => 32, + IntTy::I64 => 64, + IntTy::I128 => 128, + }, + ty::Uint(i) => match i { + UintTy::Usize => tcx.data_layout.pointer_size.bits(), + UintTy::U8 => 8, + UintTy::U16 => 16, + UintTy::U32 => 32, + UintTy::U64 => 64, + UintTy::U128 => 128, + }, + _ => 0, + } +} + +fn is_isize_or_usize(typ: Ty<'_>) -> bool { + match typ.kind { + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => true, + _ => false, + } +} + +fn span_precision_loss_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to_f64: bool) { + let mantissa_nbits = if cast_to_f64 { 52 } else { 23 }; + let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64; + let arch_dependent_str = "on targets with 64-bit wide pointers "; + let from_nbits_str = if arch_dependent { + "64".to_owned() + } else if is_isize_or_usize(cast_from) { + "32 or 64".to_owned() + } else { + int_ty_to_nbits(cast_from, cx.tcx).to_string() + }; + span_lint( + cx, + CAST_PRECISION_LOSS, + expr.span, + &format!( + "casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \ + but `{1}`'s mantissa is only {4} bits wide)", + cast_from, + if cast_to_f64 { "f64" } else { "f32" }, + if arch_dependent { arch_dependent_str } else { "" }, + from_nbits_str, + mantissa_nbits + ), + ); +} + +fn should_strip_parens(op: &Expr<'_>, snip: &str) -> bool { + if let ExprKind::Binary(_, _, _) = op.kind { + if snip.starts_with('(') && snip.ends_with(')') { + return true; + } + } + false +} + +fn span_lossless_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + // Do not suggest using From in consts/statics until it is valid to do so (see #2267). + if in_constant(cx, expr.hir_id) { + return; + } + // The suggestion is to use a function call, so if the original expression + // has parens on the outside, they are no longer needed. + let mut applicability = Applicability::MachineApplicable; + let opt = snippet_opt(cx, op.span); + let sugg = if let Some(ref snip) = opt { + if should_strip_parens(op, snip) { + &snip[1..snip.len() - 1] + } else { + snip.as_str() + } + } else { + applicability = Applicability::HasPlaceholders; + ".." + }; + + span_lint_and_sugg( + cx, + CAST_LOSSLESS, + expr.span, + &format!( + "casting `{}` to `{}` may become silently lossy if you later change the type", + cast_from, cast_to + ), + "try", + format!("{}::from({})", cast_to, sugg), + applicability, + ); +} + +enum ArchSuffix { + _32, + _64, + None, +} + +fn check_loss_of_sign(cx: &LateContext<'_, '_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !cast_from.is_signed() || cast_to.is_signed() { + return; + } + + // don't lint for positive constants + let const_val = constant(cx, &cx.tables, op); + if_chain! { + if let Some((const_val, _)) = const_val; + if let Constant::Int(n) = const_val; + if let ty::Int(ity) = cast_from.kind; + if sext(cx.tcx, n, ity) >= 0; + then { + return + } + } + + // don't lint for the result of methods that always return non-negative values + if let ExprKind::MethodCall(ref path, _, _) = op.kind { + let mut method_name = path.ident.name.as_str(); + let whitelisted_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; + + if_chain! { + if method_name == "unwrap"; + if let Some(arglist) = method_chain_args(op, &["unwrap"]); + if let ExprKind::MethodCall(ref inner_path, _, _) = &arglist[0][0].kind; + then { + method_name = inner_path.ident.name.as_str(); + } + } + + if whitelisted_methods.iter().any(|&name| method_name == name) { + return; + } + } + + span_lint( + cx, + CAST_SIGN_LOSS, + expr.span, + &format!( + "casting `{}` to `{}` may lose the sign of the value", + cast_from, cast_to + ), + ); +} + +fn check_truncation_and_wrapping(cx: &LateContext<'_, '_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let arch_64_suffix = " on targets with 64-bit wide pointers"; + let arch_32_suffix = " on targets with 32-bit wide pointers"; + let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed(); + let from_nbits = int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = int_ty_to_nbits(cast_to, cx.tcx); + let (span_truncation, suffix_truncation, span_wrap, suffix_wrap) = + match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { + (true, true) | (false, false) => ( + to_nbits < from_nbits, + ArchSuffix::None, + to_nbits == from_nbits && cast_unsigned_to_signed, + ArchSuffix::None, + ), + (true, false) => ( + to_nbits <= 32, + if to_nbits == 32 { + ArchSuffix::_64 + } else { + ArchSuffix::None + }, + to_nbits <= 32 && cast_unsigned_to_signed, + ArchSuffix::_32, + ), + (false, true) => ( + from_nbits == 64, + ArchSuffix::_32, + cast_unsigned_to_signed, + if from_nbits == 64 { + ArchSuffix::_64 + } else { + ArchSuffix::_32 + }, + ), + }; + if span_truncation { + span_lint( + cx, + CAST_POSSIBLE_TRUNCATION, + expr.span, + &format!( + "casting `{}` to `{}` may truncate the value{}", + cast_from, + cast_to, + match suffix_truncation { + ArchSuffix::_32 => arch_32_suffix, + ArchSuffix::_64 => arch_64_suffix, + ArchSuffix::None => "", + } + ), + ); + } + if span_wrap { + span_lint( + cx, + CAST_POSSIBLE_WRAP, + expr.span, + &format!( + "casting `{}` to `{}` may wrap around the value{}", + cast_from, + cast_to, + match suffix_wrap { + ArchSuffix::_32 => arch_32_suffix, + ArchSuffix::_64 => arch_64_suffix, + ArchSuffix::None => "", + } + ), + ); + } +} + +fn check_lossless(cx: &LateContext<'_, '_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); + let from_nbits = int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = int_ty_to_nbits(cast_to, cx.tcx); + if !is_isize_or_usize(cast_from) && !is_isize_or_usize(cast_to) && from_nbits < to_nbits && !cast_signed_to_unsigned + { + span_lossless_lint(cx, expr, op, cast_from, cast_to); + } +} + +declare_lint_pass!(Casts => [ + CAST_PRECISION_LOSS, + CAST_SIGN_LOSS, + CAST_POSSIBLE_TRUNCATION, + CAST_POSSIBLE_WRAP, + CAST_LOSSLESS, + UNNECESSARY_CAST, + CAST_PTR_ALIGNMENT, + FN_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, +]); + +// Check if the given type is either `core::ffi::c_void` or +// one of the platform specific `libc::::c_void` of libc. +fn is_c_void(cx: &LateContext<'_, '_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, _) = ty.kind { + let names = cx.get_def_path(adt.did); + + if names.is_empty() { + return false; + } + if names[0] == sym!(libc) || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) { + return true; + } + } + false +} + +/// Returns the mantissa bits wide of a fp type. +/// Will return 0 if the type is not a fp +fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 { + match typ.kind { + ty::Float(FloatTy::F32) => 23, + ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52, + _ => 0, + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Casts { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + if let ExprKind::Cast(ref ex, _) = expr.kind { + let (cast_from, cast_to) = (cx.tables.expr_ty(ex), cx.tables.expr_ty(expr)); + lint_fn_to_numeric_cast(cx, expr, ex, cast_from, cast_to); + if let ExprKind::Lit(ref lit) = ex.kind { + if_chain! { + if let LitKind::Int(n, _) = lit.node; + if let Some(src) = snippet_opt(cx, lit.span); + if cast_to.is_floating_point(); + if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node); + let from_nbits = 128 - n.leading_zeros(); + let to_nbits = fp_ty_mantissa_nbits(cast_to); + if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_CAST, + expr.span, + &format!("casting integer literal to `{}` is unnecessary", cast_to), + "try", + format!("{}_{}", n, cast_to), + Applicability::MachineApplicable, + ); + return; + } + } + match lit.node { + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {}, + _ => { + if cast_from.kind == cast_to.kind && !in_external_macro(cx.sess(), expr.span) { + span_lint( + cx, + UNNECESSARY_CAST, + expr.span, + &format!( + "casting to the same type is unnecessary (`{}` -> `{}`)", + cast_from, cast_to + ), + ); + } + }, + } + } + if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { + lint_numeric_casts(cx, expr, ex, cast_from, cast_to); + } + + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); + } + } +} + +fn lint_numeric_casts<'tcx>( + cx: &LateContext<'_, 'tcx>, + expr: &Expr<'tcx>, + cast_expr: &Expr<'_>, + cast_from: Ty<'tcx>, + cast_to: Ty<'tcx>, +) { + match (cast_from.is_integral(), cast_to.is_integral()) { + (true, false) => { + let from_nbits = int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind { + 32 + } else { + 64 + }; + if is_isize_or_usize(cast_from) || from_nbits >= to_nbits { + span_precision_loss_lint(cx, expr, cast_from, to_nbits == 64); + } + if from_nbits < to_nbits { + span_lossless_lint(cx, expr, cast_expr, cast_from, cast_to); + } + }, + (false, true) => { + span_lint( + cx, + CAST_POSSIBLE_TRUNCATION, + expr.span, + &format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to), + ); + if !cast_to.is_signed() { + span_lint( + cx, + CAST_SIGN_LOSS, + expr.span, + &format!( + "casting `{}` to `{}` may lose the sign of the value", + cast_from, cast_to + ), + ); + } + }, + (true, true) => { + check_loss_of_sign(cx, expr, cast_expr, cast_from, cast_to); + check_truncation_and_wrapping(cx, expr, cast_from, cast_to); + check_lossless(cx, expr, cast_expr, cast_from, cast_to); + }, + (false, false) => { + if let (&ty::Float(FloatTy::F64), &ty::Float(FloatTy::F32)) = (&cast_from.kind, &cast_to.kind) { + span_lint( + cx, + CAST_POSSIBLE_TRUNCATION, + expr.span, + "casting `f64` to `f32` may truncate the value", + ); + } + if let (&ty::Float(FloatTy::F32), &ty::Float(FloatTy::F64)) = (&cast_from.kind, &cast_to.kind) { + span_lossless_lint(cx, expr, cast_expr, cast_from, cast_to); + } + }, + } +} + +fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { + if_chain! { + if let ty::RawPtr(from_ptr_ty) = &cast_from.kind; + if let ty::RawPtr(to_ptr_ty) = &cast_to.kind; + if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty); + if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty); + if from_layout.align.abi < to_layout.align.abi; + // with c_void, we inherently need to trust the user + if !is_c_void(cx, from_ptr_ty.ty); + // when casting from a ZST, we don't know enough to properly lint + if !from_layout.is_zst(); + then { + span_lint( + cx, + CAST_PTR_ALIGNMENT, + expr.span, + &format!( + "casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)", + cast_from, + cast_to, + from_layout.align.abi.bytes(), + to_layout.align.abi.bytes(), + ), + ); + } + } +} + +fn lint_fn_to_numeric_cast( + cx: &LateContext<'_, '_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, +) { + // We only want to check casts to `ty::Uint` or `ty::Int` + match cast_to.kind { + ty::Uint(_) | ty::Int(..) => { /* continue on */ }, + _ => return, + } + match cast_from.kind { + ty::FnDef(..) | ty::FnPtr(_) => { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + + let to_nbits = int_ty_to_nbits(cast_to, cx.tcx); + if to_nbits < cx.tcx.data_layout.pointer_size.bits() { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + expr.span, + &format!( + "casting function pointer `{}` to `{}`, which truncates the value", + from_snippet, cast_to + ), + "try", + format!("{} as usize", from_snippet), + applicability, + ); + } else if cast_to.kind != ty::Uint(UintTy::Usize) { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST, + expr.span, + &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to), + "try", + format!("{} as usize", from_snippet), + applicability, + ); + } + }, + _ => {}, + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for types used in structs, parameters and `let` + /// declarations above a certain complexity threshold. + /// + /// **Why is this bad?** Too complex types make the code less readable. Consider + /// using a `type` definition to simplify them. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::rc::Rc; + /// struct Foo { + /// inner: Rc>>>, + /// } + /// ``` + pub TYPE_COMPLEXITY, + complexity, + "usage of very complex types that might be better factored into `type` definitions" +} + +pub struct TypeComplexity { + threshold: u64, +} + +impl TypeComplexity { + #[must_use] + pub fn new(threshold: u64) -> Self { + Self { threshold } + } +} + +impl_lint_pass!(TypeComplexity => [TYPE_COMPLEXITY]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeComplexity { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + _: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + _: &'tcx Body<'_>, + _: Span, + _: HirId, + ) { + self.check_fndecl(cx, decl); + } + + fn check_struct_field(&mut self, cx: &LateContext<'a, 'tcx>, field: &'tcx hir::StructField<'_>) { + // enum variants are also struct fields now + self.check_type(cx, &field.ty); + } + + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + match item.kind { + ItemKind::Static(ref ty, _, _) | ItemKind::Const(ref ty, _) => self.check_type(cx, ty), + // functions, enums, structs, impls and traits are covered + _ => (), + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem<'_>) { + match item.kind { + TraitItemKind::Const(ref ty, _) | TraitItemKind::Type(_, Some(ref ty)) => self.check_type(cx, ty), + TraitItemKind::Fn(FnSig { ref decl, .. }, TraitFn::Required(_)) => self.check_fndecl(cx, decl), + // methods with default impl are covered by check_fn + _ => (), + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem<'_>) { + match item.kind { + ImplItemKind::Const(ref ty, _) | ImplItemKind::TyAlias(ref ty) => self.check_type(cx, ty), + // methods are covered by check_fn + _ => (), + } + } + + fn check_local(&mut self, cx: &LateContext<'a, 'tcx>, local: &'tcx Local<'_>) { + if let Some(ref ty) = local.ty { + self.check_type(cx, ty); + } + } +} + +impl<'a, 'tcx> TypeComplexity { + fn check_fndecl(&self, cx: &LateContext<'a, 'tcx>, decl: &'tcx FnDecl<'_>) { + for arg in decl.inputs { + self.check_type(cx, arg); + } + if let FnRetTy::Return(ref ty) = decl.output { + self.check_type(cx, ty); + } + } + + fn check_type(&self, cx: &LateContext<'_, '_>, ty: &hir::Ty<'_>) { + if ty.span.from_expansion() { + return; + } + let score = { + let mut visitor = TypeComplexityVisitor { score: 0, nest: 1 }; + visitor.visit_ty(ty); + visitor.score + }; + + if score > self.threshold { + span_lint( + cx, + TYPE_COMPLEXITY, + ty.span, + "very complex type used. Consider factoring parts into `type` definitions", + ); + } + } +} + +/// Walks a type and assigns a complexity score to it. +struct TypeComplexityVisitor { + /// total complexity score of the type + score: u64, + /// current nesting level + nest: u64, +} + +impl<'tcx> Visitor<'tcx> for TypeComplexityVisitor { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) { + let (add_score, sub_nest) = match ty.kind { + // _, &x and *x have only small overhead; don't mess with nesting level + TyKind::Infer | TyKind::Ptr(..) | TyKind::Rptr(..) => (1, 0), + + // the "normal" components of a type: named types, arrays/tuples + TyKind::Path(..) | TyKind::Slice(..) | TyKind::Tup(..) | TyKind::Array(..) => (10 * self.nest, 1), + + // function types bring a lot of overhead + TyKind::BareFn(ref bare) if bare.abi == Abi::Rust => (50 * self.nest, 1), + + TyKind::TraitObject(ref param_bounds, _) => { + let has_lifetime_parameters = param_bounds.iter().any(|bound| { + bound.bound_generic_params.iter().any(|gen| match gen.kind { + GenericParamKind::Lifetime { .. } => true, + _ => false, + }) + }); + if has_lifetime_parameters { + // complex trait bounds like A<'a, 'b> + (50 * self.nest, 1) + } else { + // simple trait bounds like A + B + (20 * self.nest, 0) + } + }, + + _ => (0, 0), + }; + self.score += add_score; + self.nest += sub_nest; + walk_ty(self, ty); + self.nest -= sub_nest; + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for expressions where a character literal is cast + /// to `u8` and suggests using a byte literal instead. + /// + /// **Why is this bad?** In general, casting values to smaller types is + /// error-prone and should be avoided where possible. In the particular case of + /// converting a character literal to u8, it is easy to avoid by just using a + /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter + /// than `'a' as u8`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// 'x' as u8 + /// ``` + /// + /// A better version, using the byte literal: + /// + /// ```rust,ignore + /// b'x' + /// ``` + pub CHAR_LIT_AS_U8, + complexity, + "casting a character literal to `u8` truncates" +} + +declare_lint_pass!(CharLitAsU8 => [CHAR_LIT_AS_U8]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CharLitAsU8 { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if !expr.span.from_expansion(); + if let ExprKind::Cast(e, _) = &expr.kind; + if let ExprKind::Lit(l) = &e.kind; + if let LitKind::Char(c) = l.node; + if ty::Uint(UintTy::U8) == cx.tables.expr_ty(expr).kind; + then { + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability); + + span_lint_and_then( + cx, + CHAR_LIT_AS_U8, + expr.span, + "casting a character literal to `u8` truncates", + |diag| { + diag.note("`char` is four bytes wide, but `u8` is a single byte"); + + if c.is_ascii() { + diag.span_suggestion( + expr.span, + "use a byte literal instead", + format!("b{}", snippet), + applicability, + ); + } + }); + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons where one side of the relation is + /// either the minimum or maximum value for its type and warns if it involves a + /// case that is always true or always false. Only integer and boolean types are + /// checked. + /// + /// **Why is this bad?** An expression like `min <= x` may misleadingly imply + /// that it is possible for `x` to be less than the minimum. Expressions like + /// `max < x` are probably mistakes. + /// + /// **Known problems:** For `usize` the size of the current compile target will + /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such + /// a comparison to detect target pointer width will trigger this lint. One can + /// use `mem::sizeof` and compare its value or conditional compilation + /// attributes + /// like `#[cfg(target_pointer_width = "64")] ..` instead. + /// + /// **Example:** + /// + /// ```rust + /// let vec: Vec = Vec::new(); + /// if vec.len() <= 0 {} + /// if 100 > i32::MAX {} + /// ``` + pub ABSURD_EXTREME_COMPARISONS, + correctness, + "a comparison with a maximum or minimum value that is always true or false" +} + +declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]); + +enum ExtremeType { + Minimum, + Maximum, +} + +struct ExtremeExpr<'a> { + which: ExtremeType, + expr: &'a Expr<'a>, +} + +enum AbsurdComparisonResult { + AlwaysFalse, + AlwaysTrue, + InequalityImpossible, +} + +fn is_cast_between_fixed_and_target<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if let ExprKind::Cast(ref cast_exp, _) = expr.kind { + let precast_ty = cx.tables.expr_ty(cast_exp); + let cast_ty = cx.tables.expr_ty(expr); + + return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty); + } + + false +} + +fn detect_absurd_comparison<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + op: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, +) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> { + use crate::types::AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible}; + use crate::types::ExtremeType::{Maximum, Minimum}; + use crate::utils::comparisons::{normalize_comparison, Rel}; + + // absurd comparison only makes sense on primitive types + // primitive types don't implement comparison operators with each other + if cx.tables.expr_ty(lhs) != cx.tables.expr_ty(rhs) { + return None; + } + + // comparisons between fix sized types and target sized types are considered unanalyzable + if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) { + return None; + } + + let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?; + + let lx = detect_extreme_expr(cx, normalized_lhs); + let rx = detect_extreme_expr(cx, normalized_rhs); + + Some(match rel { + Rel::Lt => { + match (lx, rx) { + (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x + (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min + _ => return None, + } + }, + Rel::Le => { + match (lx, rx) { + (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x + (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x + (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min + (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max + _ => return None, + } + }, + Rel::Ne | Rel::Eq => return None, + }) +} + +fn detect_extreme_expr<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> Option> { + use crate::types::ExtremeType::{Maximum, Minimum}; + + let ty = cx.tables.expr_ty(expr); + + let cv = constant(cx, cx.tables, expr)?.0; + + let which = match (&ty.kind, cv) { + (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => Minimum, + (&ty::Int(ity), Constant::Int(i)) + if i == unsext(cx.tcx, i128::min_value() >> (128 - int_bits(cx.tcx, ity)), ity) => + { + Minimum + }, + + (&ty::Bool, Constant::Bool(true)) => Maximum, + (&ty::Int(ity), Constant::Int(i)) + if i == unsext(cx.tcx, i128::max_value() >> (128 - int_bits(cx.tcx, ity)), ity) => + { + Maximum + }, + (&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::max_value(), uty) == i => Maximum, + + _ => return None, + }; + Some(ExtremeExpr { which, expr }) +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AbsurdExtremeComparisons { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + use crate::types::AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible}; + use crate::types::ExtremeType::{Maximum, Minimum}; + + if let ExprKind::Binary(ref cmp, ref lhs, ref rhs) = expr.kind { + if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) { + if !expr.span.from_expansion() { + let msg = "this comparison involving the minimum or maximum element for this \ + type contains a case that is always true or always false"; + + let conclusion = match result { + AlwaysFalse => "this comparison is always false".to_owned(), + AlwaysTrue => "this comparison is always true".to_owned(), + InequalityImpossible => format!( + "the case where the two sides are not equal never occurs, consider using `{} == {}` \ + instead", + snippet(cx, lhs.span, "lhs"), + snippet(cx, rhs.span, "rhs") + ), + }; + + let help = format!( + "because `{}` is the {} value for this type, {}", + snippet(cx, culprit.expr.span, "x"), + match culprit.which { + Minimum => "minimum", + Maximum => "maximum", + }, + conclusion + ); + + span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); + } + } + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons where the relation is always either + /// true or false, but where one side has been upcast so that the comparison is + /// necessary. Only integer types are checked. + /// + /// **Why is this bad?** An expression like `let x : u8 = ...; (x as u32) > 300` + /// will mistakenly imply that it is possible for `x` to be outside the range of + /// `u8`. + /// + /// **Known problems:** + /// https://github.com/rust-lang/rust-clippy/issues/886 + /// + /// **Example:** + /// ```rust + /// let x: u8 = 1; + /// (x as u32) > 300; + /// ``` + pub INVALID_UPCAST_COMPARISONS, + pedantic, + "a comparison involving an upcast which is always true or false" +} + +declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]); + +#[derive(Copy, Clone, Debug, Eq)] +enum FullInt { + S(i128), + U(u128), +} + +impl FullInt { + #[allow(clippy::cast_sign_loss)] + #[must_use] + fn cmp_s_u(s: i128, u: u128) -> Ordering { + if s < 0 { + Ordering::Less + } else if u > (i128::max_value() as u128) { + Ordering::Greater + } else { + (s as u128).cmp(&u) + } + } +} + +impl PartialEq for FullInt { + #[must_use] + fn eq(&self, other: &Self) -> bool { + self.partial_cmp(other).expect("`partial_cmp` only returns `Some(_)`") == Ordering::Equal + } +} + +impl PartialOrd for FullInt { + #[must_use] + fn partial_cmp(&self, other: &Self) -> Option { + Some(match (self, other) { + (&Self::S(s), &Self::S(o)) => s.cmp(&o), + (&Self::U(s), &Self::U(o)) => s.cmp(&o), + (&Self::S(s), &Self::U(o)) => Self::cmp_s_u(s, o), + (&Self::U(s), &Self::S(o)) => Self::cmp_s_u(o, s).reverse(), + }) + } +} +impl Ord for FullInt { + #[must_use] + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other) + .expect("`partial_cmp` for FullInt can never return `None`") + } +} + +fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> { + if let ExprKind::Cast(ref cast_exp, _) = expr.kind { + let pre_cast_ty = cx.tables.expr_ty(cast_exp); + let cast_ty = cx.tables.expr_ty(expr); + // if it's a cast from i32 to u32 wrapping will invalidate all these checks + if cx.layout_of(pre_cast_ty).ok().map(|l| l.size) == cx.layout_of(cast_ty).ok().map(|l| l.size) { + return None; + } + match pre_cast_ty.kind { + ty::Int(int_ty) => Some(match int_ty { + IntTy::I8 => ( + FullInt::S(i128::from(i8::min_value())), + FullInt::S(i128::from(i8::max_value())), + ), + IntTy::I16 => ( + FullInt::S(i128::from(i16::min_value())), + FullInt::S(i128::from(i16::max_value())), + ), + IntTy::I32 => ( + FullInt::S(i128::from(i32::min_value())), + FullInt::S(i128::from(i32::max_value())), + ), + IntTy::I64 => ( + FullInt::S(i128::from(i64::min_value())), + FullInt::S(i128::from(i64::max_value())), + ), + IntTy::I128 => (FullInt::S(i128::min_value()), FullInt::S(i128::max_value())), + IntTy::Isize => ( + FullInt::S(isize::min_value() as i128), + FullInt::S(isize::max_value() as i128), + ), + }), + ty::Uint(uint_ty) => Some(match uint_ty { + UintTy::U8 => ( + FullInt::U(u128::from(u8::min_value())), + FullInt::U(u128::from(u8::max_value())), + ), + UintTy::U16 => ( + FullInt::U(u128::from(u16::min_value())), + FullInt::U(u128::from(u16::max_value())), + ), + UintTy::U32 => ( + FullInt::U(u128::from(u32::min_value())), + FullInt::U(u128::from(u32::max_value())), + ), + UintTy::U64 => ( + FullInt::U(u128::from(u64::min_value())), + FullInt::U(u128::from(u64::max_value())), + ), + UintTy::U128 => (FullInt::U(u128::min_value()), FullInt::U(u128::max_value())), + UintTy::Usize => ( + FullInt::U(usize::min_value() as u128), + FullInt::U(usize::max_value() as u128), + ), + }), + _ => None, + } + } else { + None + } +} + +fn node_as_const_fullint<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) -> Option { + let val = constant(cx, cx.tables, expr)?.0; + if let Constant::Int(const_int) = val { + match cx.tables.expr_ty(expr).kind { + ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))), + ty::Uint(_) => Some(FullInt::U(const_int)), + _ => None, + } + } else { + None + } +} + +fn err_upcast_comparison(cx: &LateContext<'_, '_>, span: Span, expr: &Expr<'_>, always: bool) { + if let ExprKind::Cast(ref cast_val, _) = expr.kind { + span_lint( + cx, + INVALID_UPCAST_COMPARISONS, + span, + &format!( + "because of the numeric bounds on `{}` prior to casting, this expression is always {}", + snippet(cx, cast_val.span, "the expression"), + if always { "true" } else { "false" }, + ), + ); + } +} + +fn upcast_comparison_bounds_err<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + span: Span, + rel: comparisons::Rel, + lhs_bounds: Option<(FullInt, FullInt)>, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, + invert: bool, +) { + use crate::utils::comparisons::Rel; + + if let Some((lb, ub)) = lhs_bounds { + if let Some(norm_rhs_val) = node_as_const_fullint(cx, rhs) { + if rel == Rel::Eq || rel == Rel::Ne { + if norm_rhs_val < lb || norm_rhs_val > ub { + err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); + } + } else if match rel { + Rel::Lt => { + if invert { + norm_rhs_val < lb + } else { + ub < norm_rhs_val + } + }, + Rel::Le => { + if invert { + norm_rhs_val <= lb + } else { + ub <= norm_rhs_val + } + }, + Rel::Eq | Rel::Ne => unreachable!(), + } { + err_upcast_comparison(cx, span, lhs, true) + } else if match rel { + Rel::Lt => { + if invert { + norm_rhs_val >= ub + } else { + lb >= norm_rhs_val + } + }, + Rel::Le => { + if invert { + norm_rhs_val > ub + } else { + lb > norm_rhs_val + } + }, + Rel::Eq | Rel::Ne => unreachable!(), + } { + err_upcast_comparison(cx, span, lhs, false) + } + } + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidUpcastComparisons { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(ref cmp, ref lhs, ref rhs) = expr.kind { + let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs); + let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized { + val + } else { + return; + }; + + let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); + let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); + + upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); + upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); + } + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for public `impl` or `fn` missing generalization + /// over different hashers and implicitly defaulting to the default hashing + /// algorithm (`SipHash`). + /// + /// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be + /// used with them. + /// + /// **Known problems:** Suggestions for replacing constructors can contain + /// false-positives. Also applying suggestions can require modification of other + /// pieces of code, possibly including external crates. + /// + /// **Example:** + /// ```rust + /// # use std::collections::HashMap; + /// # use std::hash::{Hash, BuildHasher}; + /// # trait Serialize {}; + /// impl Serialize for HashMap { } + /// + /// pub fn foo(map: &mut HashMap) { } + /// ``` + /// could be rewritten as + /// ```rust + /// # use std::collections::HashMap; + /// # use std::hash::{Hash, BuildHasher}; + /// # trait Serialize {}; + /// impl Serialize for HashMap { } + /// + /// pub fn foo(map: &mut HashMap) { } + /// ``` + pub IMPLICIT_HASHER, + pedantic, + "missing generalization over different hashers" +} + +declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitHasher { + #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + use rustc_span::BytePos; + + fn suggestion<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + diag: &mut DiagnosticBuilder<'_>, + generics_span: Span, + generics_suggestion_span: Span, + target: &ImplicitHasherType<'_>, + vis: ImplicitHasherConstructorVisitor<'_, '_, '_>, + ) { + let generics_snip = snippet(cx, generics_span, ""); + // trim `<` `>` + let generics_snip = if generics_snip.is_empty() { + "" + } else { + &generics_snip[1..generics_snip.len() - 1] + }; + + multispan_sugg( + diag, + "consider adding a type parameter".to_string(), + vec![ + ( + generics_suggestion_span, + format!( + "<{}{}S: ::std::hash::BuildHasher{}>", + generics_snip, + if generics_snip.is_empty() { "" } else { ", " }, + if vis.suggestions.is_empty() { + "" + } else { + // request users to add `Default` bound so that generic constructors can be used + " + Default" + }, + ), + ), + ( + target.span(), + format!("{}<{}, S>", target.type_name(), target.type_arguments(),), + ), + ], + ); + + if !vis.suggestions.is_empty() { + multispan_sugg(diag, "...and use generic constructor".into(), vis.suggestions); + } + } + + if !cx.access_levels.is_exported(item.hir_id) { + return; + } + + match item.kind { + ItemKind::Impl { + ref generics, + self_ty: ref ty, + ref items, + .. + } => { + let mut vis = ImplicitHasherTypeVisitor::new(cx); + vis.visit_ty(ty); + + for target in &vis.found { + if differing_macro_contexts(item.span, target.span()) { + return; + } + + let generics_suggestion_span = generics.span.substitute_dummy({ + let pos = snippet_opt(cx, item.span.until(target.span())) + .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4))); + if let Some(pos) = pos { + Span::new(pos, pos, item.span.data().ctxt) + } else { + return; + } + }); + + let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target); + for item in items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) { + ctr_vis.visit_impl_item(item); + } + + span_lint_and_then( + cx, + IMPLICIT_HASHER, + target.span(), + &format!( + "impl for `{}` should be generalized over different hashers", + target.type_name() + ), + move |diag| { + suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis); + }, + ); + } + }, + ItemKind::Fn(ref sig, ref generics, body_id) => { + let body = cx.tcx.hir().body(body_id); + + for ty in sig.decl.inputs { + let mut vis = ImplicitHasherTypeVisitor::new(cx); + vis.visit_ty(ty); + + for target in &vis.found { + if in_external_macro(cx.sess(), generics.span) { + continue; + } + let generics_suggestion_span = generics.span.substitute_dummy({ + let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span)) + .and_then(|snip| { + let i = snip.find("fn")?; + Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32)) + }) + .expect("failed to create span for type parameters"); + Span::new(pos, pos, item.span.data().ctxt) + }); + + let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target); + ctr_vis.visit_body(body); + + span_lint_and_then( + cx, + IMPLICIT_HASHER, + target.span(), + &format!( + "parameter of type `{}` should be generalized over different hashers", + target.type_name() + ), + move |diag| { + suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis); + }, + ); + } + } + }, + _ => {}, + } + } +} + +enum ImplicitHasherType<'tcx> { + HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>), + HashSet(Span, Ty<'tcx>, Cow<'static, str>), +} + +impl<'tcx> ImplicitHasherType<'tcx> { + /// Checks that `ty` is a target type without a `BuildHasher`. + fn new<'a>(cx: &LateContext<'a, 'tcx>, hir_ty: &hir::Ty<'_>) -> Option { + if let TyKind::Path(QPath::Resolved(None, ref path)) = hir_ty.kind { + let params: Vec<_> = path + .segments + .last() + .as_ref()? + .args + .as_ref()? + .args + .iter() + .filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + .collect(); + let params_len = params.len(); + + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + + if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) && params_len == 2 { + Some(ImplicitHasherType::HashMap( + hir_ty.span, + ty, + snippet(cx, params[0].span, "K"), + snippet(cx, params[1].span, "V"), + )) + } else if is_type_diagnostic_item(cx, ty, sym!(hashset_type)) && params_len == 1 { + Some(ImplicitHasherType::HashSet( + hir_ty.span, + ty, + snippet(cx, params[0].span, "T"), + )) + } else { + None + } + } else { + None + } + } + + fn type_name(&self) -> &'static str { + match *self { + ImplicitHasherType::HashMap(..) => "HashMap", + ImplicitHasherType::HashSet(..) => "HashSet", + } + } + + fn type_arguments(&self) -> String { + match *self { + ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v), + ImplicitHasherType::HashSet(.., ref t) => format!("{}", t), + } + } + + fn ty(&self) -> Ty<'tcx> { + match *self { + ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty, + } + } + + fn span(&self) -> Span { + match *self { + ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span, + } + } +} + +struct ImplicitHasherTypeVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + found: Vec>, +} + +impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'a, 'tcx>) -> Self { + Self { cx, found: vec![] } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) { + if let Some(target) = ImplicitHasherType::new(self.cx, t) { + self.found.push(target); + } + + walk_ty(self, t); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Looks for default-hasher-dependent constructors like `HashMap::new`. +struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + body: &'a TypeckTables<'tcx>, + target: &'b ImplicitHasherType<'tcx>, + suggestions: BTreeMap, +} + +impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { + fn new(cx: &'a LateContext<'a, 'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self { + Self { + cx, + body: cx.tables, + target, + suggestions: BTreeMap::new(), + } + } +} + +impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { + type Map = Map<'tcx>; + + fn visit_body(&mut self, body: &'tcx Body<'_>) { + let prev_body = self.body; + self.body = self.cx.tcx.body_tables(body.id()); + walk_body(self, body); + self.body = prev_body; + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref fun, ref args) = e.kind; + if let ExprKind::Path(QPath::TypeRelative(ref ty, ref method)) = fun.kind; + if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind; + then { + if !same_tys(self.cx, self.target.ty(), self.body.expr_ty(e)) { + return; + } + + if match_path(ty_path, &paths::HASHMAP) { + if method.ident.name == sym!(new) { + self.suggestions + .insert(e.span, "HashMap::default()".to_string()); + } else if method.ident.name == sym!(with_capacity) { + self.suggestions.insert( + e.span, + format!( + "HashMap::with_capacity_and_hasher({}, Default::default())", + snippet(self.cx, args[0].span, "capacity"), + ), + ); + } + } else if match_path(ty_path, &paths::HASHSET) { + if method.ident.name == sym!(new) { + self.suggestions + .insert(e.span, "HashSet::default()".to_string()); + } else if method.ident.name == sym!(with_capacity) { + self.suggestions.insert( + e.span, + format!( + "HashSet::with_capacity_and_hasher({}, Default::default())", + snippet(self.cx, args[0].span, "capacity"), + ), + ); + } + } + } + } + + walk_expr(self, e); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of `&T` to `&mut T` anywhere in the code. + /// + /// **Why is this bad?** It’s basically guaranteed to be undefined behaviour. + /// `UnsafeCell` is the only way to obtain aliasable data that is considered + /// mutable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// fn x(r: &i32) { + /// unsafe { + /// *(r as *const _ as *mut _) += 1; + /// } + /// } + /// ``` + /// + /// Instead consider using interior mutability types. + /// + /// ```rust + /// use std::cell::UnsafeCell; + /// + /// fn x(r: &UnsafeCell) { + /// unsafe { + /// *r.get() += 1; + /// } + /// } + /// ``` + pub CAST_REF_TO_MUT, + correctness, + "a cast of reference to a mutable pointer" +} + +declare_lint_pass!(RefToMut => [CAST_REF_TO_MUT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RefToMut { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Unary(UnOp::UnDeref, e) = &expr.kind; + if let ExprKind::Cast(e, t) = &e.kind; + if let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind; + if let ExprKind::Cast(e, t) = &e.kind; + if let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind; + if let ty::Ref(..) = cx.tables.node_type(e.hir_id).kind; + then { + span_lint( + cx, + CAST_REF_TO_MUT, + expr.span, + "casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell`", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unicode.rs b/src/tools/clippy/clippy_lints/src/unicode.rs new file mode 100644 index 0000000000000..d073c197656c6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unicode.rs @@ -0,0 +1,131 @@ +use crate::utils::{is_allowed, snippet, span_lint_and_sugg}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, HirId}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use unicode_normalization::UnicodeNormalization; + +declare_clippy_lint! { + /// **What it does:** Checks for the Unicode zero-width space in the code. + /// + /// **Why is this bad?** Having an invisible character in the code makes for all + /// sorts of April fools, but otherwise is very much frowned upon. + /// + /// **Known problems:** None. + /// + /// **Example:** You don't see it, but there may be a zero-width space + /// somewhere in this text. + pub ZERO_WIDTH_SPACE, + correctness, + "using a zero-width space in a string literal, which is confusing" +} + +declare_clippy_lint! { + /// **What it does:** Checks for non-ASCII characters in string literals. + /// + /// **Why is this bad?** Yeah, we know, the 90's called and wanted their charset + /// back. Even so, there still are editors and other programs out there that + /// don't work well with Unicode. So if the code is meant to be used + /// internationally, on multiple operating systems, or has other portability + /// requirements, activating this lint could be useful. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = String::from("€"); + /// ``` + /// Could be written as: + /// ```rust + /// let x = String::from("\u{20ac}"); + /// ``` + pub NON_ASCII_LITERAL, + pedantic, + "using any literal non-ASCII chars in a string literal instead of using the `\\u` escape" +} + +declare_clippy_lint! { + /// **What it does:** Checks for string literals that contain Unicode in a form + /// that is not equal to its + /// [NFC-recomposition](http://www.unicode.org/reports/tr15/#Norm_Forms). + /// + /// **Why is this bad?** If such a string is compared to another, the results + /// may be surprising. + /// + /// **Known problems** None. + /// + /// **Example:** You may not see it, but "à"" and "à"" aren't the same string. The + /// former when escaped is actually `"a\u{300}"` while the latter is `"\u{e0}"`. + pub UNICODE_NOT_NFC, + pedantic, + "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)" +} + +declare_lint_pass!(Unicode => [ZERO_WIDTH_SPACE, NON_ASCII_LITERAL, UNICODE_NOT_NFC]); + +impl LateLintPass<'_, '_> for Unicode { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &'_ Expr<'_>) { + if let ExprKind::Lit(ref lit) = expr.kind { + if let LitKind::Str(_, _) = lit.node { + check_str(cx, lit.span, expr.hir_id) + } + } + } +} + +fn escape>(s: T) -> String { + let mut result = String::new(); + for c in s { + if c as u32 > 0x7F { + for d in c.escape_unicode() { + result.push(d) + } + } else { + result.push(c); + } + } + result +} + +fn check_str(cx: &LateContext<'_, '_>, span: Span, id: HirId) { + let string = snippet(cx, span, ""); + if string.contains('\u{200B}') { + span_lint_and_sugg( + cx, + ZERO_WIDTH_SPACE, + span, + "zero-width space detected", + "consider replacing the string with", + string.replace("\u{200B}", "\\u{200B}"), + Applicability::MachineApplicable, + ); + } + if string.chars().any(|c| c as u32 > 0x7F) { + span_lint_and_sugg( + cx, + NON_ASCII_LITERAL, + span, + "literal non-ASCII character detected", + "consider replacing the string with", + if is_allowed(cx, UNICODE_NOT_NFC, id) { + escape(string.chars()) + } else { + escape(string.nfc()) + }, + Applicability::MachineApplicable, + ); + } + if is_allowed(cx, NON_ASCII_LITERAL, id) && string.chars().zip(string.nfc()).any(|(a, b)| a != b) { + span_lint_and_sugg( + cx, + UNICODE_NOT_NFC, + span, + "non-NFC Unicode sequence detected", + "consider replacing the string with", + string.nfc().collect::(), + Applicability::MachineApplicable, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/unnamed_address.rs b/src/tools/clippy/clippy_lints/src/unnamed_address.rs new file mode 100644 index 0000000000000..4e077b95b5c68 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unnamed_address.rs @@ -0,0 +1,135 @@ +use crate::utils::{match_def_path, paths, span_lint, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons with an address of a function item. + /// + /// **Why is this bad?** Function item address is not guaranteed to be unique and could vary + /// between different code generation units. Furthermore different function items could have + /// the same address after being merged together. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// type F = fn(); + /// fn a() {} + /// let f: F = a; + /// if f == a { + /// // ... + /// } + /// ``` + pub FN_ADDRESS_COMPARISONS, + correctness, + "comparison with an address of a function item" +} + +declare_clippy_lint! { + /// **What it does:** Checks for comparisons with an address of a trait vtable. + /// + /// **Why is this bad?** Comparing trait objects pointers compares an vtable addresses which + /// are not guaranteed to be unique and could vary between different code generation units. + /// Furthermore vtables for different types could have the same address after being merged + /// together. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,ignore + /// let a: Rc = ... + /// let b: Rc = ... + /// if Rc::ptr_eq(&a, &b) { + /// ... + /// } + /// ``` + pub VTABLE_ADDRESS_COMPARISONS, + correctness, + "comparison with an address of a trait vtable" +} + +declare_lint_pass!(UnnamedAddress => [FN_ADDRESS_COMPARISONS, VTABLE_ADDRESS_COMPARISONS]); + +impl LateLintPass<'_, '_> for UnnamedAddress { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + fn is_comparison(binop: BinOpKind) -> bool { + match binop { + BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Ge | BinOpKind::Gt => true, + _ => false, + } + } + + fn is_trait_ptr(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + match cx.tables.expr_ty_adjusted(expr).kind { + ty::RawPtr(ty::TypeAndMut { ty, .. }) => ty.is_trait(), + _ => false, + } + } + + fn is_fn_def(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + if let ty::FnDef(..) = cx.tables.expr_ty(expr).kind { + true + } else { + false + } + } + + if_chain! { + if let ExprKind::Binary(binop, ref left, ref right) = expr.kind; + if is_comparison(binop.node); + if is_trait_ptr(cx, left) && is_trait_ptr(cx, right); + then { + span_lint_and_help( + cx, + VTABLE_ADDRESS_COMPARISONS, + expr.span, + "comparing trait object pointers compares a non-unique vtable address", + None, + "consider extracting and comparing data pointers only", + ); + } + } + + if_chain! { + if let ExprKind::Call(ref func, [ref _left, ref _right]) = expr.kind; + if let ExprKind::Path(ref func_qpath) = func.kind; + if let Some(def_id) = cx.tables.qpath_res(func_qpath, func.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::PTR_EQ) || + match_def_path(cx, def_id, &paths::RC_PTR_EQ) || + match_def_path(cx, def_id, &paths::ARC_PTR_EQ); + let ty_param = cx.tables.node_substs(func.hir_id).type_at(0); + if ty_param.is_trait(); + then { + span_lint_and_help( + cx, + VTABLE_ADDRESS_COMPARISONS, + expr.span, + "comparing trait object pointers compares a non-unique vtable address", + None, + "consider extracting and comparing data pointers only", + ); + } + } + + if_chain! { + if let ExprKind::Binary(binop, ref left, ref right) = expr.kind; + if is_comparison(binop.node); + if cx.tables.expr_ty_adjusted(left).is_fn_ptr() && + cx.tables.expr_ty_adjusted(right).is_fn_ptr(); + if is_fn_def(cx, left) || is_fn_def(cx, right); + then { + span_lint( + cx, + FN_ADDRESS_COMPARISONS, + expr.span, + "comparing with a non-unique address of a function item", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs new file mode 100644 index 0000000000000..86c469a4dccf7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unsafe_removed_from_name.rs @@ -0,0 +1,78 @@ +use crate::utils::span_lint; +use rustc_ast::ast::{Ident, Item, ItemKind, UseTree, UseTreeKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::symbol::SymbolStr; + +declare_clippy_lint! { + /// **What it does:** Checks for imports that remove "unsafe" from an item's + /// name. + /// + /// **Why is this bad?** Renaming makes it less clear which traits and + /// structures are unsafe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// use std::cell::{UnsafeCell as TotallySafeCell}; + /// + /// extern crate crossbeam; + /// use crossbeam::{spawn_unsafe as spawn}; + /// ``` + pub UNSAFE_REMOVED_FROM_NAME, + style, + "`unsafe` removed from API names on import" +} + +declare_lint_pass!(UnsafeNameRemoval => [UNSAFE_REMOVED_FROM_NAME]); + +impl EarlyLintPass for UnsafeNameRemoval { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::Use(ref use_tree) = item.kind { + check_use_tree(use_tree, cx, item.span); + } + } +} + +fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) { + match use_tree.kind { + UseTreeKind::Simple(Some(new_name), ..) => { + let old_name = use_tree + .prefix + .segments + .last() + .expect("use paths cannot be empty") + .ident; + unsafe_to_safe_check(old_name, new_name, cx, span); + }, + UseTreeKind::Simple(None, ..) | UseTreeKind::Glob => {}, + UseTreeKind::Nested(ref nested_use_tree) => { + for &(ref use_tree, _) in nested_use_tree { + check_use_tree(use_tree, cx, span); + } + }, + } +} + +fn unsafe_to_safe_check(old_name: Ident, new_name: Ident, cx: &EarlyContext<'_>, span: Span) { + let old_str = old_name.name.as_str(); + let new_str = new_name.name.as_str(); + if contains_unsafe(&old_str) && !contains_unsafe(&new_str) { + span_lint( + cx, + UNSAFE_REMOVED_FROM_NAME, + span, + &format!( + "removed `unsafe` from the name of `{}` in use as `{}`", + old_str, new_str + ), + ); + } +} + +#[must_use] +fn contains_unsafe(name: &SymbolStr) -> bool { + name.contains("Unsafe") || name.contains("unsafe") +} diff --git a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs new file mode 100644 index 0000000000000..b85134e3d7a9a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs @@ -0,0 +1,91 @@ +use crate::utils::{is_try, match_qpath, match_trait_method, paths, span_lint}; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for unused written/read amount. + /// + /// **Why is this bad?** `io::Write::write(_vectored)` and + /// `io::Read::read(_vectored)` are not guaranteed to + /// process the entire buffer. They return how many bytes were processed, which + /// might be smaller + /// than a given buffer's length. If you don't need to deal with + /// partial-write/read, use + /// `write_all`/`read_exact` instead. + /// + /// **Known problems:** Detects only common patterns. + /// + /// **Example:** + /// ```rust,ignore + /// use std::io; + /// fn foo(w: &mut W) -> io::Result<()> { + /// // must be `w.write_all(b"foo")?;` + /// w.write(b"foo")?; + /// Ok(()) + /// } + /// ``` + pub UNUSED_IO_AMOUNT, + correctness, + "unused written/read amount" +} + +declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedIoAmount { + fn check_stmt(&mut self, cx: &LateContext<'_, '_>, s: &hir::Stmt<'_>) { + let expr = match s.kind { + hir::StmtKind::Semi(ref expr) | hir::StmtKind::Expr(ref expr) => &**expr, + _ => return, + }; + + match expr.kind { + hir::ExprKind::Match(ref res, _, _) if is_try(expr).is_some() => { + if let hir::ExprKind::Call(ref func, ref args) = res.kind { + if let hir::ExprKind::Path(ref path) = func.kind { + if match_qpath(path, &paths::TRY_INTO_RESULT) && args.len() == 1 { + check_method_call(cx, &args[0], expr); + } + } + } else { + check_method_call(cx, res, expr); + } + }, + + hir::ExprKind::MethodCall(ref path, _, ref args) => match &*path.ident.as_str() { + "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => { + check_method_call(cx, &args[0], expr); + }, + _ => (), + }, + + _ => (), + } + } +} + +fn check_method_call(cx: &LateContext<'_, '_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) { + if let hir::ExprKind::MethodCall(ref path, _, _) = call.kind { + let symbol = &*path.ident.as_str(); + let read_trait = match_trait_method(cx, call, &paths::IO_READ); + let write_trait = match_trait_method(cx, call, &paths::IO_WRITE); + + match (read_trait, write_trait, symbol) { + (true, _, "read") => span_lint( + cx, + UNUSED_IO_AMOUNT, + expr.span, + "read amount is not handled. Use `Read::read_exact` instead", + ), + (true, _, "read_vectored") => span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled"), + (_, true, "write") => span_lint( + cx, + UNUSED_IO_AMOUNT, + expr.span, + "written amount is not handled. Use `Write::write_all` instead", + ), + (_, true, "write_vectored") => span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled"), + _ => (), + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/unused_self.rs b/src/tools/clippy/clippy_lints/src/unused_self.rs new file mode 100644 index 0000000000000..3d5e2f9fd2155 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unused_self.rs @@ -0,0 +1,105 @@ +use if_chain::if_chain; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{walk_path, NestedVisitorMap, Visitor}; +use rustc_hir::{HirId, ImplItem, ImplItemKind, ItemKind, Path}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks methods that contain a `self` argument but don't use it + /// + /// **Why is this bad?** It may be clearer to define the method as an associated function instead + /// of an instance method if it doesn't require `self`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// struct A; + /// impl A { + /// fn method(&self) {} + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust,ignore + /// struct A; + /// impl A { + /// fn method() {} + /// } + /// ``` + pub UNUSED_SELF, + pedantic, + "methods that contain a `self` argument but don't use it" +} + +declare_lint_pass!(UnusedSelf => [UNUSED_SELF]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnusedSelf { + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &ImplItem<'_>) { + if impl_item.span.from_expansion() { + return; + } + let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id); + let parent_item = cx.tcx.hir().expect_item(parent); + let def_id = cx.tcx.hir().local_def_id(impl_item.hir_id); + let assoc_item = cx.tcx.associated_item(def_id); + if_chain! { + if let ItemKind::Impl { of_trait: None, .. } = parent_item.kind; + if assoc_item.fn_has_self_parameter; + if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; + let body = cx.tcx.hir().body(*body_id); + if !body.params.is_empty(); + then { + let self_param = &body.params[0]; + let self_hir_id = self_param.pat.hir_id; + let mut visitor = UnusedSelfVisitor { + cx, + uses_self: false, + self_hir_id: &self_hir_id, + }; + visitor.visit_body(body); + if !visitor.uses_self { + span_lint_and_help( + cx, + UNUSED_SELF, + self_param.span, + "unused `self` argument", + None, + "consider refactoring to a associated function", + ); + return; + } + } + } + } +} + +struct UnusedSelfVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + uses_self: bool, + self_hir_id: &'a HirId, +} + +impl<'a, 'tcx> Visitor<'tcx> for UnusedSelfVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + if self.uses_self { + // This function already uses `self` + return; + } + if let Res::Local(hir_id) = &path.res { + self.uses_self = self.self_hir_id == hir_id + } + walk_path(self, path); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/unwrap.rs b/src/tools/clippy/clippy_lints/src/unwrap.rs new file mode 100644 index 0000000000000..5235c98efab13 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/unwrap.rs @@ -0,0 +1,216 @@ +use crate::utils::{higher::if_block, is_type_diagnostic_item, span_lint_and_then, usage::is_potentially_mutated}; +use if_chain::if_chain; +use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Path, QPath, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for calls of `unwrap[_err]()` that cannot fail. + /// + /// **Why is this bad?** Using `if let` or `match` is more idiomatic. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let option = Some(0); + /// # fn do_something_with(_x: usize) {} + /// if option.is_some() { + /// do_something_with(option.unwrap()) + /// } + /// ``` + /// + /// Could be written: + /// + /// ```rust + /// # let option = Some(0); + /// # fn do_something_with(_x: usize) {} + /// if let Some(value) = option { + /// do_something_with(value) + /// } + /// ``` + pub UNNECESSARY_UNWRAP, + complexity, + "checks for calls of `unwrap[_err]()` that cannot fail" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls of `unwrap[_err]()` that will always fail. + /// + /// **Why is this bad?** If panicking is desired, an explicit `panic!()` should be used. + /// + /// **Known problems:** This lint only checks `if` conditions not assignments. + /// So something like `let x: Option<()> = None; x.unwrap();` will not be recognized. + /// + /// **Example:** + /// ```rust + /// # let option = Some(0); + /// # fn do_something_with(_x: usize) {} + /// if option.is_none() { + /// do_something_with(option.unwrap()) + /// } + /// ``` + /// + /// This code will always panic. The if condition should probably be inverted. + pub PANICKING_UNWRAP, + correctness, + "checks for calls of `unwrap[_err]()` that will always fail" +} + +/// Visitor that keeps track of which variables are unwrappable. +struct UnwrappableVariablesVisitor<'a, 'tcx> { + unwrappables: Vec>, + cx: &'a LateContext<'a, 'tcx>, +} +/// Contains information about whether a variable can be unwrapped. +#[derive(Copy, Clone, Debug)] +struct UnwrapInfo<'tcx> { + /// The variable that is checked + ident: &'tcx Path<'tcx>, + /// The check, like `x.is_ok()` + check: &'tcx Expr<'tcx>, + /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`). + safe_to_unwrap: bool, +} + +/// Collects the information about unwrappable variables from an if condition +/// The `invert` argument tells us whether the condition is negated. +fn collect_unwrap_info<'a, 'tcx>( + cx: &'a LateContext<'a, 'tcx>, + expr: &'tcx Expr<'_>, + invert: bool, +) -> Vec> { + if let ExprKind::Binary(op, left, right) = &expr.kind { + match (invert, op.node) { + (false, BinOpKind::And) | (false, BinOpKind::BitAnd) | (true, BinOpKind::Or) | (true, BinOpKind::BitOr) => { + let mut unwrap_info = collect_unwrap_info(cx, left, invert); + unwrap_info.append(&mut collect_unwrap_info(cx, right, invert)); + return unwrap_info; + }, + _ => (), + } + } else if let ExprKind::Unary(UnOp::UnNot, expr) = &expr.kind { + return collect_unwrap_info(cx, expr, !invert); + } else { + if_chain! { + if let ExprKind::MethodCall(method_name, _, args) = &expr.kind; + if let ExprKind::Path(QPath::Resolved(None, path)) = &args[0].kind; + let ty = cx.tables.expr_ty(&args[0]); + if is_type_diagnostic_item(cx, ty, sym!(option_type)) || is_type_diagnostic_item(cx, ty, sym!(result_type)); + let name = method_name.ident.as_str(); + if ["is_some", "is_none", "is_ok", "is_err"].contains(&&*name); + then { + assert!(args.len() == 1); + let unwrappable = match name.as_ref() { + "is_some" | "is_ok" => true, + "is_err" | "is_none" => false, + _ => unreachable!(), + }; + let safe_to_unwrap = unwrappable != invert; + return vec![UnwrapInfo { ident: path, check: expr, safe_to_unwrap }]; + } + } + } + Vec::new() +} + +impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> { + fn visit_branch(&mut self, cond: &'tcx Expr<'_>, branch: &'tcx Expr<'_>, else_branch: bool) { + let prev_len = self.unwrappables.len(); + for unwrap_info in collect_unwrap_info(self.cx, cond, else_branch) { + if is_potentially_mutated(unwrap_info.ident, cond, self.cx) + || is_potentially_mutated(unwrap_info.ident, branch, self.cx) + { + // if the variable is mutated, we don't know whether it can be unwrapped: + continue; + } + self.unwrappables.push(unwrap_info); + } + walk_expr(self, branch); + self.unwrappables.truncate(prev_len); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + // Shouldn't lint when `expr` is in macro. + if in_external_macro(self.cx.tcx.sess, expr.span) { + return; + } + if let Some((cond, then, els)) = if_block(&expr) { + walk_expr(self, cond); + self.visit_branch(cond, then, false); + if let Some(els) = els { + self.visit_branch(cond, els, true); + } + } else { + // find `unwrap[_err]()` calls: + if_chain! { + if let ExprKind::MethodCall(ref method_name, _, ref args) = expr.kind; + if let ExprKind::Path(QPath::Resolved(None, ref path)) = args[0].kind; + if [sym!(unwrap), sym!(unwrap_err)].contains(&method_name.ident.name); + let call_to_unwrap = method_name.ident.name == sym!(unwrap); + if let Some(unwrappable) = self.unwrappables.iter() + .find(|u| u.ident.res == path.res); + then { + if call_to_unwrap == unwrappable.safe_to_unwrap { + span_lint_and_then( + self.cx, + UNNECESSARY_UNWRAP, + expr.span, + &format!("You checked before that `{}()` cannot fail. \ + Instead of checking and unwrapping, it's better to use `if let` or `match`.", + method_name.ident.name), + |diag| { diag.span_label(unwrappable.check.span, "the check is happening here"); }, + ); + } else { + span_lint_and_then( + self.cx, + PANICKING_UNWRAP, + expr.span, + &format!("This call to `{}()` will always panic.", + method_name.ident.name), + |diag| { diag.span_label(unwrappable.check.span, "because of this check"); }, + ); + } + } + } + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Unwrap { + fn check_fn( + &mut self, + cx: &LateContext<'a, 'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + span: Span, + fn_id: HirId, + ) { + if span.from_expansion() { + return; + } + + let mut v = UnwrappableVariablesVisitor { + cx, + unwrappables: Vec::new(), + }; + + walk_fn(&mut v, kind, decl, body.id(), span, fn_id); + } +} diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs new file mode 100644 index 0000000000000..f8e1aff33e773 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/use_self.rs @@ -0,0 +1,272 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_item, walk_path, walk_ty, NestedVisitorMap, Visitor}; +use rustc_hir::{ + def, FnDecl, FnRetTy, FnSig, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Path, PathSegment, QPath, + TyKind, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_middle::ty::{DefIdTree, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; +use rustc_typeck::hir_ty_to_ty; + +use crate::utils::{differing_macro_contexts, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for unnecessary repetition of structure name when a + /// replacement with `Self` is applicable. + /// + /// **Why is this bad?** Unnecessary repetition. Mixed use of `Self` and struct + /// name + /// feels inconsistent. + /// + /// **Known problems:** + /// - False positive when using associated types (#2843) + /// - False positives in some situations when using generics (#3410) + /// + /// **Example:** + /// ```rust + /// struct Foo {} + /// impl Foo { + /// fn new() -> Foo { + /// Foo {} + /// } + /// } + /// ``` + /// could be + /// ```rust + /// struct Foo {} + /// impl Foo { + /// fn new() -> Self { + /// Self {} + /// } + /// } + /// ``` + pub USE_SELF, + nursery, + "Unnecessary structure name repetition whereas `Self` is applicable" +} + +declare_lint_pass!(UseSelf => [USE_SELF]); + +const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element"; + +fn span_use_self_lint(cx: &LateContext<'_, '_>, path: &Path<'_>, last_segment: Option<&PathSegment<'_>>) { + let last_segment = last_segment.unwrap_or_else(|| path.segments.last().expect(SEGMENTS_MSG)); + + // Path segments only include actual path, no methods or fields. + let last_path_span = last_segment.ident.span; + + if differing_macro_contexts(path.span, last_path_span) { + return; + } + + // Only take path up to the end of last_path_span. + let span = path.span.with_hi(last_path_span.hi()); + + span_lint_and_sugg( + cx, + USE_SELF, + span, + "unnecessary structure name repetition", + "use the applicable keyword", + "Self".to_owned(), + Applicability::MachineApplicable, + ); +} + +// FIXME: always use this (more correct) visitor, not just in method signatures. +struct SemanticUseSelfVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + self_ty: Ty<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'_>) { + if let TyKind::Path(QPath::Resolved(_, path)) = &hir_ty.kind { + match path.res { + def::Res::SelfTy(..) => {}, + _ => { + if hir_ty_to_ty(self.cx.tcx, hir_ty) == self.self_ty { + span_use_self_lint(self.cx, path, None); + } + }, + } + } + + walk_ty(self, hir_ty) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn check_trait_method_impl_decl<'a, 'tcx>( + cx: &'a LateContext<'a, 'tcx>, + impl_item: &ImplItem<'_>, + impl_decl: &'tcx FnDecl<'_>, + impl_trait_ref: ty::TraitRef<'tcx>, +) { + let trait_method = cx + .tcx + .associated_items(impl_trait_ref.def_id) + .find_by_name_and_kind(cx.tcx, impl_item.ident, ty::AssocKind::Fn, impl_trait_ref.def_id) + .expect("impl method matches a trait method"); + + let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id); + let trait_method_sig = cx.tcx.erase_late_bound_regions(&trait_method_sig); + + let output_hir_ty = if let FnRetTy::Return(ty) = &impl_decl.output { + Some(&**ty) + } else { + None + }; + + // `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature. + // `trait_ty` (of type `ty::Ty`) is the semantic type for the signature in the trait. + // We use `impl_hir_ty` to see if the type was written as `Self`, + // `hir_ty_to_ty(...)` to check semantic types of paths, and + // `trait_ty` to determine which parts of the signature in the trait, mention + // the type being implemented verbatim (as opposed to `Self`). + for (impl_hir_ty, trait_ty) in impl_decl + .inputs + .iter() + .chain(output_hir_ty) + .zip(trait_method_sig.inputs_and_output) + { + // Check if the input/output type in the trait method specifies the implemented + // type verbatim, and only suggest `Self` if that isn't the case. + // This avoids suggestions to e.g. replace `Vec` with `Vec`, + // in an `impl Trait for u8`, when the trait always uses `Vec`. + // See also https://github.com/rust-lang/rust-clippy/issues/2894. + let self_ty = impl_trait_ref.self_ty(); + if !trait_ty.walk().any(|inner| inner == self_ty.into()) { + let mut visitor = SemanticUseSelfVisitor { cx, self_ty }; + + visitor.visit_ty(&impl_hir_ty); + } + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UseSelf { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if in_external_macro(cx.sess(), item.span) { + return; + } + if_chain! { + if let ItemKind::Impl{ self_ty: ref item_type, items: refs, .. } = item.kind; + if let TyKind::Path(QPath::Resolved(_, ref item_path)) = item_type.kind; + then { + let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args; + let should_check = if let Some(ref params) = *parameters { + !params.parenthesized && !params.args.iter().any(|arg| match arg { + GenericArg::Lifetime(_) => true, + _ => false, + }) + } else { + true + }; + + if should_check { + let visitor = &mut UseSelfVisitor { + item_path, + cx, + }; + let impl_def_id = cx.tcx.hir().local_def_id(item.hir_id); + let impl_trait_ref = cx.tcx.impl_trait_ref(impl_def_id); + + if let Some(impl_trait_ref) = impl_trait_ref { + for impl_item_ref in refs { + let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id); + if let ImplItemKind::Fn(FnSig{ decl: impl_decl, .. }, impl_body_id) + = &impl_item.kind { + check_trait_method_impl_decl(cx, impl_item, impl_decl, impl_trait_ref); + + let body = cx.tcx.hir().body(*impl_body_id); + visitor.visit_body(body); + } else { + visitor.visit_impl_item(impl_item); + } + } + } else { + for impl_item_ref in refs { + let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id); + visitor.visit_impl_item(impl_item); + } + } + } + } + } + } +} + +struct UseSelfVisitor<'a, 'tcx> { + item_path: &'a Path<'a>, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for UseSelfVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) { + if !path.segments.iter().any(|p| p.ident.span.is_dummy()) { + if path.segments.len() >= 2 { + let last_but_one = &path.segments[path.segments.len() - 2]; + if last_but_one.ident.name != kw::SelfUpper { + let enum_def_id = match path.res { + Res::Def(DefKind::Variant, variant_def_id) => self.cx.tcx.parent(variant_def_id), + Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), ctor_def_id) => { + let variant_def_id = self.cx.tcx.parent(ctor_def_id); + variant_def_id.and_then(|def_id| self.cx.tcx.parent(def_id)) + }, + _ => None, + }; + + if self.item_path.res.opt_def_id() == enum_def_id { + span_use_self_lint(self.cx, path, Some(last_but_one)); + } + } + } + + if path.segments.last().expect(SEGMENTS_MSG).ident.name != kw::SelfUpper { + if self.item_path.res == path.res { + span_use_self_lint(self.cx, path, None); + } else if let Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), ctor_def_id) = path.res { + if self.item_path.res.opt_def_id() == self.cx.tcx.parent(ctor_def_id) { + span_use_self_lint(self.cx, path, None); + } + } + } + } + + walk_path(self, path); + } + + fn visit_item(&mut self, item: &'tcx Item<'_>) { + match item.kind { + ItemKind::Use(..) + | ItemKind::Static(..) + | ItemKind::Enum(..) + | ItemKind::Struct(..) + | ItemKind::Union(..) + | ItemKind::Impl { .. } + | ItemKind::Fn(..) => { + // Don't check statements that shadow `Self` or where `Self` can't be used + }, + _ => walk_item(self, item), + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/attrs.rs b/src/tools/clippy/clippy_lints/src/utils/attrs.rs new file mode 100644 index 0000000000000..4dcf6c105ec63 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/attrs.rs @@ -0,0 +1,128 @@ +use rustc_ast::ast; +use rustc_ast::expand::is_proc_macro_attr; +use rustc_errors::Applicability; +use rustc_session::Session; +use std::str::FromStr; + +/// Deprecation status of attributes known by Clippy. +#[allow(dead_code)] +pub enum DeprecationStatus { + /// Attribute is deprecated + Deprecated, + /// Attribute is deprecated and was replaced by the named attribute + Replaced(&'static str), + None, +} + +pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[ + ("author", DeprecationStatus::None), + ("cognitive_complexity", DeprecationStatus::None), + ( + "cyclomatic_complexity", + DeprecationStatus::Replaced("cognitive_complexity"), + ), + ("dump", DeprecationStatus::None), +]; + +pub struct LimitStack { + stack: Vec, +} + +impl Drop for LimitStack { + fn drop(&mut self) { + assert_eq!(self.stack.len(), 1); + } +} + +impl LimitStack { + #[must_use] + pub fn new(limit: u64) -> Self { + Self { stack: vec![limit] } + } + pub fn limit(&self) -> u64 { + *self.stack.last().expect("there should always be a value in the stack") + } + pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| stack.push(val)); + } + pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val))); + } +} + +pub fn get_attr<'a>( + sess: &'a Session, + attrs: &'a [ast::Attribute], + name: &'static str, +) -> impl Iterator { + attrs.iter().filter(move |attr| { + let attr = if let ast::AttrKind::Normal(ref attr) = attr.kind { + attr + } else { + return false; + }; + let attr_segments = &attr.path.segments; + if attr_segments.len() == 2 && attr_segments[0].ident.to_string() == "clippy" { + if let Some(deprecation_status) = + BUILTIN_ATTRIBUTES + .iter() + .find_map(|(builtin_name, deprecation_status)| { + if *builtin_name == attr_segments[1].ident.to_string() { + Some(deprecation_status) + } else { + None + } + }) + { + let mut diag = sess.struct_span_err(attr_segments[1].ident.span, "Usage of deprecated attribute"); + match *deprecation_status { + DeprecationStatus::Deprecated => { + diag.emit(); + false + }, + DeprecationStatus::Replaced(new_name) => { + diag.span_suggestion( + attr_segments[1].ident.span, + "consider using", + new_name.to_string(), + Applicability::MachineApplicable, + ); + diag.emit(); + false + }, + DeprecationStatus::None => { + diag.cancel(); + attr_segments[1].ident.to_string() == name + }, + } + } else { + sess.span_err(attr_segments[1].ident.span, "Usage of unknown attribute"); + false + } + } else { + false + } + }) +} + +fn parse_attrs(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) { + for attr in get_attr(sess, attrs, name) { + if let Some(ref value) = attr.value_str() { + if let Ok(value) = FromStr::from_str(&value.as_str()) { + f(value) + } else { + sess.span_err(attr.span, "not a number"); + } + } else { + sess.span_err(attr.span, "bad clippy attribute"); + } + } +} + +/// Return true if the attributes contain any of `proc_macro`, +/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise +pub fn is_proc_macro(attrs: &[ast::Attribute]) -> bool { + attrs.iter().any(is_proc_macro_attr) +} diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs new file mode 100644 index 0000000000000..74601008dca41 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -0,0 +1,757 @@ +//! A group of attributes that can be attached to Rust code in order +//! to generate a clippy lint detecting said code automatically. + +use crate::utils::{get_attr, higher}; +use rustc_ast::ast::{Attribute, LitFloatType, LitKind}; +use rustc_ast::walk_list; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind, QPath, Stmt, StmtKind, TyKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_session::Session; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Generates clippy code that detects the offending pattern + /// + /// **Example:** + /// ```rust,ignore + /// // ./tests/ui/my_lint.rs + /// fn foo() { + /// // detect the following pattern + /// #[clippy::author] + /// if x == 42 { + /// // but ignore everything from here on + /// #![clippy::author = "ignore"] + /// } + /// () + /// } + /// ``` + /// + /// Running `TESTNAME=ui/my_lint cargo uitest` will produce + /// a `./tests/ui/new_lint.stdout` file with the generated code: + /// + /// ```rust,ignore + /// // ./tests/ui/new_lint.stdout + /// if_chain! { + /// if let ExprKind::If(ref cond, ref then, None) = item.kind, + /// if let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind, + /// if let ExprKind::Path(ref path) = left.kind, + /// if let ExprKind::Lit(ref lit) = right.kind, + /// if let LitKind::Int(42, _) = lit.node, + /// then { + /// // report your lint here + /// } + /// } + /// ``` + pub LINT_AUTHOR, + internal_warn, + "helper for writing lints" +} + +declare_lint_pass!(Author => [LINT_AUTHOR]); + +fn prelude() { + println!("if_chain! {{"); +} + +fn done() { + println!(" then {{"); + println!(" // report your lint here"); + println!(" }}"); + println!("}}"); +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Author { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + if !has_attr(cx.sess(), &item.attrs) { + return; + } + prelude(); + PrintVisitor::new("item").visit_item(item); + done(); + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'_>) { + if !has_attr(cx.sess(), &item.attrs) { + return; + } + prelude(); + PrintVisitor::new("item").visit_impl_item(item); + done(); + } + + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'_>) { + if !has_attr(cx.sess(), &item.attrs) { + return; + } + prelude(); + PrintVisitor::new("item").visit_trait_item(item); + done(); + } + + fn check_variant(&mut self, cx: &LateContext<'a, 'tcx>, var: &'tcx hir::Variant<'_>) { + if !has_attr(cx.sess(), &var.attrs) { + return; + } + prelude(); + let parent_hir_id = cx.tcx.hir().get_parent_node(var.id); + PrintVisitor::new("var").visit_variant(var, &hir::Generics::empty(), parent_hir_id); + done(); + } + + fn check_struct_field(&mut self, cx: &LateContext<'a, 'tcx>, field: &'tcx hir::StructField<'_>) { + if !has_attr(cx.sess(), &field.attrs) { + return; + } + prelude(); + PrintVisitor::new("field").visit_struct_field(field); + done(); + } + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if !has_attr(cx.sess(), &expr.attrs) { + return; + } + prelude(); + PrintVisitor::new("expr").visit_expr(expr); + done(); + } + + fn check_arm(&mut self, cx: &LateContext<'a, 'tcx>, arm: &'tcx hir::Arm<'_>) { + if !has_attr(cx.sess(), &arm.attrs) { + return; + } + prelude(); + PrintVisitor::new("arm").visit_arm(arm); + done(); + } + + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx hir::Stmt<'_>) { + if !has_attr(cx.sess(), stmt.kind.attrs()) { + return; + } + prelude(); + PrintVisitor::new("stmt").visit_stmt(stmt); + done(); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ForeignItem<'_>) { + if !has_attr(cx.sess(), &item.attrs) { + return; + } + prelude(); + PrintVisitor::new("item").visit_foreign_item(item); + done(); + } +} + +impl PrintVisitor { + #[must_use] + fn new(s: &'static str) -> Self { + Self { + ids: FxHashMap::default(), + current: s.to_owned(), + } + } + + fn next(&mut self, s: &'static str) -> String { + use std::collections::hash_map::Entry::{Occupied, Vacant}; + match self.ids.entry(s) { + // already there: start numbering from `1` + Occupied(mut occ) => { + let val = occ.get_mut(); + *val += 1; + format!("{}{}", s, *val) + }, + // not there: insert and return name as given + Vacant(vac) => { + vac.insert(0); + s.to_owned() + }, + } + } + + fn print_qpath(&mut self, path: &QPath<'_>) { + print!(" if match_qpath({}, &[", self.current); + print_path(path, &mut true); + println!("]);"); + } +} + +struct PrintVisitor { + /// Fields are the current index that needs to be appended to pattern + /// binding names + ids: FxHashMap<&'static str, usize>, + /// the name that needs to be destructured + current: String, +} + +impl<'tcx> Visitor<'tcx> for PrintVisitor { + type Map = Map<'tcx>; + + #[allow(clippy::too_many_lines)] + fn visit_expr(&mut self, expr: &Expr<'_>) { + // handle if desugarings + // TODO add more desugarings here + if let Some((cond, then, opt_else)) = higher::if_block(&expr) { + let cond_pat = self.next("cond"); + let then_pat = self.next("then"); + if let Some(else_) = opt_else { + let else_pat = self.next("else_"); + println!( + " if let Some((ref {}, ref {}, Some({}))) = higher::if_block(&{});", + cond_pat, then_pat, else_pat, self.current + ); + self.current = else_pat; + self.visit_expr(else_); + } else { + println!( + " if let Some((ref {}, ref {}, None)) = higher::if_block(&{});", + cond_pat, then_pat, self.current + ); + } + self.current = cond_pat; + self.visit_expr(cond); + self.current = then_pat; + self.visit_expr(then); + return; + } + + print!(" if let ExprKind::"); + let current = format!("{}.kind", self.current); + match expr.kind { + ExprKind::Box(ref inner) => { + let inner_pat = self.next("inner"); + println!("Box(ref {}) = {};", inner_pat, current); + self.current = inner_pat; + self.visit_expr(inner); + }, + ExprKind::Array(ref elements) => { + let elements_pat = self.next("elements"); + println!("Array(ref {}) = {};", elements_pat, current); + println!(" if {}.len() == {};", elements_pat, elements.len()); + for (i, element) in elements.iter().enumerate() { + self.current = format!("{}[{}]", elements_pat, i); + self.visit_expr(element); + } + }, + ExprKind::Call(ref func, ref args) => { + let func_pat = self.next("func"); + let args_pat = self.next("args"); + println!("Call(ref {}, ref {}) = {};", func_pat, args_pat, current); + self.current = func_pat; + self.visit_expr(func); + println!(" if {}.len() == {};", args_pat, args.len()); + for (i, arg) in args.iter().enumerate() { + self.current = format!("{}[{}]", args_pat, i); + self.visit_expr(arg); + } + }, + ExprKind::MethodCall(ref _method_name, ref _generics, ref _args) => { + println!("MethodCall(ref method_name, ref generics, ref args) = {};", current); + println!(" // unimplemented: `ExprKind::MethodCall` is not further destructured at the moment"); + }, + ExprKind::Tup(ref elements) => { + let elements_pat = self.next("elements"); + println!("Tup(ref {}) = {};", elements_pat, current); + println!(" if {}.len() == {};", elements_pat, elements.len()); + for (i, element) in elements.iter().enumerate() { + self.current = format!("{}[{}]", elements_pat, i); + self.visit_expr(element); + } + }, + ExprKind::Binary(ref op, ref left, ref right) => { + let op_pat = self.next("op"); + let left_pat = self.next("left"); + let right_pat = self.next("right"); + println!( + "Binary(ref {}, ref {}, ref {}) = {};", + op_pat, left_pat, right_pat, current + ); + println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat); + self.current = left_pat; + self.visit_expr(left); + self.current = right_pat; + self.visit_expr(right); + }, + ExprKind::Unary(ref op, ref inner) => { + let inner_pat = self.next("inner"); + println!("Unary(UnOp::{:?}, ref {}) = {};", op, inner_pat, current); + self.current = inner_pat; + self.visit_expr(inner); + }, + ExprKind::Lit(ref lit) => { + let lit_pat = self.next("lit"); + println!("Lit(ref {}) = {};", lit_pat, current); + match lit.node { + LitKind::Bool(val) => println!(" if let LitKind::Bool({:?}) = {}.node;", val, lit_pat), + LitKind::Char(c) => println!(" if let LitKind::Char({:?}) = {}.node;", c, lit_pat), + LitKind::Err(val) => println!(" if let LitKind::Err({}) = {}.node;", val, lit_pat), + LitKind::Byte(b) => println!(" if let LitKind::Byte({}) = {}.node;", b, lit_pat), + // FIXME: also check int type + LitKind::Int(i, _) => println!(" if let LitKind::Int({}, _) = {}.node;", i, lit_pat), + LitKind::Float(_, LitFloatType::Suffixed(_)) => println!( + " if let LitKind::Float(_, LitFloatType::Suffixed(_)) = {}.node;", + lit_pat + ), + LitKind::Float(_, LitFloatType::Unsuffixed) => println!( + " if let LitKind::Float(_, LitFloatType::Unsuffixed) = {}.node;", + lit_pat + ), + LitKind::ByteStr(ref vec) => { + let vec_pat = self.next("vec"); + println!(" if let LitKind::ByteStr(ref {}) = {}.node;", vec_pat, lit_pat); + println!(" if let [{:?}] = **{};", vec, vec_pat); + }, + LitKind::Str(ref text, _) => { + let str_pat = self.next("s"); + println!(" if let LitKind::Str(ref {}, _) = {}.node;", str_pat, lit_pat); + println!(" if {}.as_str() == {:?}", str_pat, &*text.as_str()) + }, + } + }, + ExprKind::Cast(ref expr, ref ty) => { + let cast_pat = self.next("expr"); + let cast_ty = self.next("cast_ty"); + let qp_label = self.next("qp"); + + println!("Cast(ref {}, ref {}) = {};", cast_pat, cast_ty, current); + if let TyKind::Path(ref qp) = ty.kind { + println!(" if let TyKind::Path(ref {}) = {}.kind;", qp_label, cast_ty); + self.current = qp_label; + self.print_qpath(qp); + } + self.current = cast_pat; + self.visit_expr(expr); + }, + ExprKind::Type(ref expr, ref _ty) => { + let cast_pat = self.next("expr"); + println!("Type(ref {}, _) = {};", cast_pat, current); + self.current = cast_pat; + self.visit_expr(expr); + }, + ExprKind::Loop(ref body, _, desugaring) => { + let body_pat = self.next("body"); + let des = loop_desugaring_name(desugaring); + let label_pat = self.next("label"); + println!("Loop(ref {}, ref {}, {}) = {};", body_pat, label_pat, des, current); + self.current = body_pat; + self.visit_block(body); + }, + ExprKind::Match(ref expr, ref arms, desugaring) => { + let des = desugaring_name(desugaring); + let expr_pat = self.next("expr"); + let arms_pat = self.next("arms"); + println!("Match(ref {}, ref {}, {}) = {};", expr_pat, arms_pat, des, current); + self.current = expr_pat; + self.visit_expr(expr); + println!(" if {}.len() == {};", arms_pat, arms.len()); + for (i, arm) in arms.iter().enumerate() { + self.current = format!("{}[{}].body", arms_pat, i); + self.visit_expr(&arm.body); + if let Some(ref guard) = arm.guard { + let guard_pat = self.next("guard"); + println!(" if let Some(ref {}) = {}[{}].guard;", guard_pat, arms_pat, i); + match guard { + hir::Guard::If(ref if_expr) => { + let if_expr_pat = self.next("expr"); + println!(" if let Guard::If(ref {}) = {};", if_expr_pat, guard_pat); + self.current = if_expr_pat; + self.visit_expr(if_expr); + }, + } + } + self.current = format!("{}[{}].pat", arms_pat, i); + self.visit_pat(&arm.pat); + } + }, + ExprKind::Closure(ref _capture_clause, ref _func, _, _, _) => { + println!("Closure(ref capture_clause, ref func, _, _, _) = {};", current); + println!(" // unimplemented: `ExprKind::Closure` is not further destructured at the moment"); + }, + ExprKind::Yield(ref sub, _) => { + let sub_pat = self.next("sub"); + println!("Yield(ref sub) = {};", current); + self.current = sub_pat; + self.visit_expr(sub); + }, + ExprKind::Block(ref block, _) => { + let block_pat = self.next("block"); + println!("Block(ref {}) = {};", block_pat, current); + self.current = block_pat; + self.visit_block(block); + }, + ExprKind::Assign(ref target, ref value, _) => { + let target_pat = self.next("target"); + let value_pat = self.next("value"); + println!( + "Assign(ref {}, ref {}, ref _span) = {};", + target_pat, value_pat, current + ); + self.current = target_pat; + self.visit_expr(target); + self.current = value_pat; + self.visit_expr(value); + }, + ExprKind::AssignOp(ref op, ref target, ref value) => { + let op_pat = self.next("op"); + let target_pat = self.next("target"); + let value_pat = self.next("value"); + println!( + "AssignOp(ref {}, ref {}, ref {}) = {};", + op_pat, target_pat, value_pat, current + ); + println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat); + self.current = target_pat; + self.visit_expr(target); + self.current = value_pat; + self.visit_expr(value); + }, + ExprKind::Field(ref object, ref field_ident) => { + let obj_pat = self.next("object"); + let field_name_pat = self.next("field_name"); + println!("Field(ref {}, ref {}) = {};", obj_pat, field_name_pat, current); + println!(" if {}.as_str() == {:?}", field_name_pat, field_ident.as_str()); + self.current = obj_pat; + self.visit_expr(object); + }, + ExprKind::Index(ref object, ref index) => { + let object_pat = self.next("object"); + let index_pat = self.next("index"); + println!("Index(ref {}, ref {}) = {};", object_pat, index_pat, current); + self.current = object_pat; + self.visit_expr(object); + self.current = index_pat; + self.visit_expr(index); + }, + ExprKind::Path(ref path) => { + let path_pat = self.next("path"); + println!("Path(ref {}) = {};", path_pat, current); + self.current = path_pat; + self.print_qpath(path); + }, + ExprKind::AddrOf(kind, mutability, ref inner) => { + let inner_pat = self.next("inner"); + println!( + "AddrOf(BorrowKind::{:?}, Mutability::{:?}, ref {}) = {};", + kind, mutability, inner_pat, current + ); + self.current = inner_pat; + self.visit_expr(inner); + }, + ExprKind::Break(ref _destination, ref opt_value) => { + let destination_pat = self.next("destination"); + if let Some(ref value) = *opt_value { + let value_pat = self.next("value"); + println!("Break(ref {}, Some(ref {})) = {};", destination_pat, value_pat, current); + self.current = value_pat; + self.visit_expr(value); + } else { + println!("Break(ref {}, None) = {};", destination_pat, current); + } + // FIXME: implement label printing + }, + ExprKind::Continue(ref _destination) => { + let destination_pat = self.next("destination"); + println!("Again(ref {}) = {};", destination_pat, current); + // FIXME: implement label printing + }, + ExprKind::Ret(ref opt_value) => { + if let Some(ref value) = *opt_value { + let value_pat = self.next("value"); + println!("Ret(Some(ref {})) = {};", value_pat, current); + self.current = value_pat; + self.visit_expr(value); + } else { + println!("Ret(None) = {};", current); + } + }, + ExprKind::LlvmInlineAsm(_) => { + println!("LlvmInlineAsm(_) = {};", current); + println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment"); + }, + ExprKind::Struct(ref path, ref fields, ref opt_base) => { + let path_pat = self.next("path"); + let fields_pat = self.next("fields"); + if let Some(ref base) = *opt_base { + let base_pat = self.next("base"); + println!( + "Struct(ref {}, ref {}, Some(ref {})) = {};", + path_pat, fields_pat, base_pat, current + ); + self.current = base_pat; + self.visit_expr(base); + } else { + println!("Struct(ref {}, ref {}, None) = {};", path_pat, fields_pat, current); + } + self.current = path_pat; + self.print_qpath(path); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + // FIXME: compute length (needs type info) + ExprKind::Repeat(ref value, _) => { + let value_pat = self.next("value"); + println!("Repeat(ref {}, _) = {};", value_pat, current); + println!("// unimplemented: repeat count check"); + self.current = value_pat; + self.visit_expr(value); + }, + ExprKind::Err => { + println!("Err = {}", current); + }, + ExprKind::DropTemps(ref expr) => { + let expr_pat = self.next("expr"); + println!("DropTemps(ref {}) = {};", expr_pat, current); + self.current = expr_pat; + self.visit_expr(expr); + }, + } + } + + fn visit_block(&mut self, block: &Block<'_>) { + let trailing_pat = self.next("trailing_expr"); + println!(" if let Some({}) = &{}.expr;", trailing_pat, self.current); + println!(" if {}.stmts.len() == {};", self.current, block.stmts.len()); + let current = self.current.clone(); + for (i, stmt) in block.stmts.iter().enumerate() { + self.current = format!("{}.stmts[{}]", current, i); + self.visit_stmt(stmt); + } + } + + #[allow(clippy::too_many_lines)] + fn visit_pat(&mut self, pat: &Pat<'_>) { + print!(" if let PatKind::"); + let current = format!("{}.kind", self.current); + match pat.kind { + PatKind::Wild => println!("Wild = {};", current), + PatKind::Binding(anno, .., ident, ref sub) => { + let anno_pat = match anno { + BindingAnnotation::Unannotated => "BindingAnnotation::Unannotated", + BindingAnnotation::Mutable => "BindingAnnotation::Mutable", + BindingAnnotation::Ref => "BindingAnnotation::Ref", + BindingAnnotation::RefMut => "BindingAnnotation::RefMut", + }; + let name_pat = self.next("name"); + if let Some(ref sub) = *sub { + let sub_pat = self.next("sub"); + println!( + "Binding({}, _, {}, Some(ref {})) = {};", + anno_pat, name_pat, sub_pat, current + ); + self.current = sub_pat; + self.visit_pat(sub); + } else { + println!("Binding({}, _, {}, None) = {};", anno_pat, name_pat, current); + } + println!(" if {}.as_str() == \"{}\";", name_pat, ident.as_str()); + }, + PatKind::Struct(ref path, ref fields, ignore) => { + let path_pat = self.next("path"); + let fields_pat = self.next("fields"); + println!( + "Struct(ref {}, ref {}, {}) = {};", + path_pat, fields_pat, ignore, current + ); + self.current = path_pat; + self.print_qpath(path); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::Or(ref fields) => { + let fields_pat = self.next("fields"); + println!("Or(ref {}) = {};", fields_pat, current); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::TupleStruct(ref path, ref fields, skip_pos) => { + let path_pat = self.next("path"); + let fields_pat = self.next("fields"); + println!( + "TupleStruct(ref {}, ref {}, {:?}) = {};", + path_pat, fields_pat, skip_pos, current + ); + self.current = path_pat; + self.print_qpath(path); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::Path(ref path) => { + let path_pat = self.next("path"); + println!("Path(ref {}) = {};", path_pat, current); + self.current = path_pat; + self.print_qpath(path); + }, + PatKind::Tuple(ref fields, skip_pos) => { + let fields_pat = self.next("fields"); + println!("Tuple(ref {}, {:?}) = {};", fields_pat, skip_pos, current); + println!(" if {}.len() == {};", fields_pat, fields.len()); + println!(" // unimplemented: field checks"); + }, + PatKind::Box(ref pat) => { + let pat_pat = self.next("pat"); + println!("Box(ref {}) = {};", pat_pat, current); + self.current = pat_pat; + self.visit_pat(pat); + }, + PatKind::Ref(ref pat, muta) => { + let pat_pat = self.next("pat"); + println!("Ref(ref {}, Mutability::{:?}) = {};", pat_pat, muta, current); + self.current = pat_pat; + self.visit_pat(pat); + }, + PatKind::Lit(ref lit_expr) => { + let lit_expr_pat = self.next("lit_expr"); + println!("Lit(ref {}) = {}", lit_expr_pat, current); + self.current = lit_expr_pat; + self.visit_expr(lit_expr); + }, + PatKind::Range(ref start, ref end, end_kind) => { + let start_pat = self.next("start"); + let end_pat = self.next("end"); + println!( + "Range(ref {}, ref {}, RangeEnd::{:?}) = {};", + start_pat, end_pat, end_kind, current + ); + self.current = start_pat; + walk_list!(self, visit_expr, start); + self.current = end_pat; + walk_list!(self, visit_expr, end); + }, + PatKind::Slice(ref start, ref middle, ref end) => { + let start_pat = self.next("start"); + let end_pat = self.next("end"); + if let Some(ref middle) = middle { + let middle_pat = self.next("middle"); + println!( + "Slice(ref {}, Some(ref {}), ref {}) = {};", + start_pat, middle_pat, end_pat, current + ); + self.current = middle_pat; + self.visit_pat(middle); + } else { + println!("Slice(ref {}, None, ref {}) = {};", start_pat, end_pat, current); + } + println!(" if {}.len() == {};", start_pat, start.len()); + for (i, pat) in start.iter().enumerate() { + self.current = format!("{}[{}]", start_pat, i); + self.visit_pat(pat); + } + println!(" if {}.len() == {};", end_pat, end.len()); + for (i, pat) in end.iter().enumerate() { + self.current = format!("{}[{}]", end_pat, i); + self.visit_pat(pat); + } + }, + } + } + + fn visit_stmt(&mut self, s: &Stmt<'_>) { + print!(" if let StmtKind::"); + let current = format!("{}.kind", self.current); + match s.kind { + // A local (let) binding: + StmtKind::Local(ref local) => { + let local_pat = self.next("local"); + println!("Local(ref {}) = {};", local_pat, current); + if let Some(ref init) = local.init { + let init_pat = self.next("init"); + println!(" if let Some(ref {}) = {}.init;", init_pat, local_pat); + self.current = init_pat; + self.visit_expr(init); + } + self.current = format!("{}.pat", local_pat); + self.visit_pat(&local.pat); + }, + // An item binding: + StmtKind::Item(_) => { + println!("Item(item_id) = {};", current); + }, + + // Expr without trailing semi-colon (must have unit type): + StmtKind::Expr(ref e) => { + let e_pat = self.next("e"); + println!("Expr(ref {}, _) = {}", e_pat, current); + self.current = e_pat; + self.visit_expr(e); + }, + + // Expr with trailing semi-colon (may have any type): + StmtKind::Semi(ref e) => { + let e_pat = self.next("e"); + println!("Semi(ref {}, _) = {}", e_pat, current); + self.current = e_pat; + self.visit_expr(e); + }, + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn has_attr(sess: &Session, attrs: &[Attribute]) -> bool { + get_attr(sess, attrs, "author").count() > 0 +} + +#[must_use] +fn desugaring_name(des: hir::MatchSource) -> String { + match des { + hir::MatchSource::ForLoopDesugar => "MatchSource::ForLoopDesugar".to_string(), + hir::MatchSource::TryDesugar => "MatchSource::TryDesugar".to_string(), + hir::MatchSource::WhileDesugar => "MatchSource::WhileDesugar".to_string(), + hir::MatchSource::WhileLetDesugar => "MatchSource::WhileLetDesugar".to_string(), + hir::MatchSource::Normal => "MatchSource::Normal".to_string(), + hir::MatchSource::IfLetDesugar { contains_else_clause } => format!( + "MatchSource::IfLetDesugar {{ contains_else_clause: {} }}", + contains_else_clause + ), + hir::MatchSource::IfDesugar { contains_else_clause } => format!( + "MatchSource::IfDesugar {{ contains_else_clause: {} }}", + contains_else_clause + ), + hir::MatchSource::AwaitDesugar => "MatchSource::AwaitDesugar".to_string(), + } +} + +#[must_use] +fn loop_desugaring_name(des: hir::LoopSource) -> &'static str { + match des { + hir::LoopSource::ForLoop => "LoopSource::ForLoop", + hir::LoopSource::Loop => "LoopSource::Loop", + hir::LoopSource::While => "LoopSource::While", + hir::LoopSource::WhileLet => "LoopSource::WhileLet", + } +} + +fn print_path(path: &QPath<'_>, first: &mut bool) { + match *path { + QPath::Resolved(_, ref path) => { + for segment in path.segments { + if *first { + *first = false; + } else { + print!(", "); + } + print!("{:?}", segment.ident.as_str()); + } + }, + QPath::TypeRelative(ref ty, ref segment) => match ty.kind { + hir::TyKind::Path(ref inner_path) => { + print_path(inner_path, first); + if *first { + *first = false; + } else { + print!(", "); + } + print!("{:?}", segment.ident.as_str()); + }, + ref other => print!("/* unimplemented: {:?}*/", other), + }, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/camel_case.rs b/src/tools/clippy/clippy_lints/src/utils/camel_case.rs new file mode 100644 index 0000000000000..4192a26d3c800 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/camel_case.rs @@ -0,0 +1,115 @@ +/// Returns the index of the character after the first camel-case component of `s`. +#[must_use] +pub fn until(s: &str) -> usize { + let mut iter = s.char_indices(); + if let Some((_, first)) = iter.next() { + if !first.is_uppercase() { + return 0; + } + } else { + return 0; + } + let mut up = true; + let mut last_i = 0; + for (i, c) in iter { + if up { + if c.is_lowercase() { + up = false; + } else { + return last_i; + } + } else if c.is_uppercase() { + up = true; + last_i = i; + } else if !c.is_lowercase() { + return i; + } + } + if up { + last_i + } else { + s.len() + } +} + +/// Returns index of the last camel-case component of `s`. +#[must_use] +pub fn from(s: &str) -> usize { + let mut iter = s.char_indices().rev(); + if let Some((_, first)) = iter.next() { + if !first.is_lowercase() { + return s.len(); + } + } else { + return s.len(); + } + let mut down = true; + let mut last_i = s.len(); + for (i, c) in iter { + if down { + if c.is_uppercase() { + down = false; + last_i = i; + } else if !c.is_lowercase() { + return last_i; + } + } else if c.is_lowercase() { + down = true; + } else { + return last_i; + } + } + last_i +} + +#[cfg(test)] +mod test { + use super::{from, until}; + + #[test] + fn from_full() { + assert_eq!(from("AbcDef"), 0); + assert_eq!(from("Abc"), 0); + } + + #[test] + fn from_partial() { + assert_eq!(from("abcDef"), 3); + assert_eq!(from("aDbc"), 1); + } + + #[test] + fn from_not() { + assert_eq!(from("AbcDef_"), 7); + assert_eq!(from("AbcDD"), 5); + } + + #[test] + fn from_caps() { + assert_eq!(from("ABCD"), 4); + } + + #[test] + fn until_full() { + assert_eq!(until("AbcDef"), 6); + assert_eq!(until("Abc"), 3); + } + + #[test] + fn until_not() { + assert_eq!(until("abcDef"), 0); + assert_eq!(until("aDbc"), 0); + } + + #[test] + fn until_partial() { + assert_eq!(until("AbcDef_"), 6); + assert_eq!(until("CallTypeC"), 8); + assert_eq!(until("AbcDD"), 3); + } + + #[test] + fn until_caps() { + assert_eq!(until("ABCD"), 0); + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/comparisons.rs b/src/tools/clippy/clippy_lints/src/utils/comparisons.rs new file mode 100644 index 0000000000000..7a18d5e818fb1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/comparisons.rs @@ -0,0 +1,36 @@ +//! Utility functions about comparison operators. + +#![deny(clippy::missing_docs_in_private_items)] + +use rustc_hir::{BinOpKind, Expr}; + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +/// Represent a normalized comparison operator. +pub enum Rel { + /// `<` + Lt, + /// `<=` + Le, + /// `==` + Eq, + /// `!=` + Ne, +} + +/// Put the expression in the form `lhs < rhs`, `lhs <= rhs`, `lhs == rhs` or +/// `lhs != rhs`. +pub fn normalize_comparison<'a>( + op: BinOpKind, + lhs: &'a Expr<'a>, + rhs: &'a Expr<'a>, +) -> Option<(Rel, &'a Expr<'a>, &'a Expr<'a>)> { + match op { + BinOpKind::Lt => Some((Rel::Lt, lhs, rhs)), + BinOpKind::Le => Some((Rel::Le, lhs, rhs)), + BinOpKind::Gt => Some((Rel::Lt, rhs, lhs)), + BinOpKind::Ge => Some((Rel::Le, rhs, lhs)), + BinOpKind::Eq => Some((Rel::Eq, rhs, lhs)), + BinOpKind::Ne => Some((Rel::Ne, rhs, lhs)), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs new file mode 100644 index 0000000000000..4b81ff33495c7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -0,0 +1,240 @@ +//! Read configurations files. + +#![deny(clippy::missing_docs_in_private_items)] + +use lazy_static::lazy_static; +use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem}; +use rustc_span::source_map; +use source_map::Span; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; +use std::{env, fmt, fs, io}; + +/// Gets the configuration file from arguments. +pub fn file_from_args(args: &[NestedMetaItem]) -> Result, (&'static str, Span)> { + for arg in args.iter().filter_map(NestedMetaItem::meta_item) { + if arg.check_name(sym!(conf_file)) { + return match arg.kind { + MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)), + MetaItemKind::NameValue(ref value) => { + if let LitKind::Str(ref file, _) = value.kind { + Ok(Some(file.to_string().into())) + } else { + Err(("`conf_file` value must be a string", value.span)) + } + }, + }; + } + } + + Ok(None) +} + +/// Error from reading a configuration file. +#[derive(Debug)] +pub enum Error { + /// An I/O error. + Io(io::Error), + /// Not valid toml or doesn't fit the expected config format + Toml(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(err) => err.fmt(f), + Self::Toml(err) => err.fmt(f), + } + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Self::Io(e) + } +} + +lazy_static! { + static ref ERRORS: Mutex> = Mutex::new(Vec::new()); +} + +macro_rules! define_Conf { + ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => { + mod helpers { + use serde::Deserialize; + /// Type used to store lint configuration. + #[derive(Deserialize)] + #[serde(rename_all = "kebab-case", deny_unknown_fields)] + pub struct Conf { + $( + #[$doc] + #[serde(default = $config_str)] + #[serde(with = $config_str)] + pub $config: $Ty, + )+ + #[allow(dead_code)] + #[serde(default)] + third_party: Option<::toml::Value>, + } + + $( + mod $config { + use serde::Deserialize; + pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> { + use super::super::{ERRORS, Error}; + Ok( + <$Ty>::deserialize(deserializer).unwrap_or_else(|e| { + ERRORS + .lock() + .expect("no threading here") + .push(Error::Toml(e.to_string())); + super::$config() + }) + ) + } + } + + #[must_use] + fn $config() -> $Ty { + let x = $default; + x + } + )+ + } + }; +} + +pub use self::helpers::Conf; +define_Conf! { + /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about + (blacklisted_names, "blacklisted_names": Vec, ["foo", "bar", "baz", "quux"].iter().map(ToString::to_string).collect()), + /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have + (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25), + /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. + (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option, None), + /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks + (doc_valid_idents, "doc_valid_idents": Vec, [ + "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "DirectX", + "ECMAScript", + "GPLv2", "GPLv3", + "GitHub", "GitLab", + "IPv4", "IPv6", + "JavaScript", + "NaN", "NaNs", + "OAuth", + "OpenGL", "OpenSSH", "OpenSSL", "OpenStreetMap", + "TrueType", + "iOS", "macOS", + "TeX", "LaTeX", "BibTeX", "BibLaTeX", + "MinGW", + "CamelCase", + ].iter().map(ToString::to_string).collect()), + /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have + (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7), + /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have + (type_complexity_threshold, "type_complexity_threshold": u64, 250), + /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have + (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4), + /// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap + (too_large_for_stack, "too_large_for_stack": u64, 200), + /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger + (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3), + /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion + (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200), + /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' + (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1), + /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals + (literal_representation_threshold, "literal_representation_threshold": u64, 16384), + /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. + (trivial_copy_size_limit, "trivial_copy_size_limit": Option, None), + /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have + (too_many_lines_threshold, "too_many_lines_threshold": u64, 100), + /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack + (array_size_threshold, "array_size_threshold": u64, 512_000), + /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed + (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096), + /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have + (max_struct_bools, "max_struct_bools": u64, 3), + /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have + (max_fn_params_bools, "max_fn_params_bools": u64, 3), +} + +impl Default for Conf { + #[must_use] + fn default() -> Self { + toml::from_str("").expect("we never error on empty config files") + } +} + +/// Search for the configuration file. +pub fn lookup_conf_file() -> io::Result> { + /// Possible filename to search for. + const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"]; + + // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR. + // If neither of those exist, use ".". + let mut current = env::var_os("CLIPPY_CONF_DIR") + .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) + .map_or_else(|| PathBuf::from("."), PathBuf::from); + loop { + for config_file_name in &CONFIG_FILE_NAMES { + let config_file = current.join(config_file_name); + match fs::metadata(&config_file) { + // Only return if it's a file to handle the unlikely situation of a directory named + // `clippy.toml`. + Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)), + // Return the error if it's something other than `NotFound`; otherwise we didn't + // find the project file yet, and continue searching. + Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e), + _ => {}, + } + } + + // If the current directory has no parent, we're done searching. + if !current.pop() { + return Ok(None); + } + } +} + +/// Produces a `Conf` filled with the default values and forwards the errors +/// +/// Used internally for convenience +fn default(errors: Vec) -> (Conf, Vec) { + (Conf::default(), errors) +} + +/// Read the `toml` configuration file. +/// +/// In case of error, the function tries to continue as much as possible. +pub fn read(path: &Path) -> (Conf, Vec) { + let content = match fs::read_to_string(path) { + Ok(content) => content, + Err(err) => return default(vec![err.into()]), + }; + + assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty()); + match toml::from_str(&content) { + Ok(toml) => { + let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0); + + let toml_ref: &Conf = &toml; + + let cyc_field: Option = toml_ref.cyclomatic_complexity_threshold; + + if cyc_field.is_some() { + let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string(); + errors.push(Error::Toml(cyc_err)); + } + + (toml, errors) + }, + Err(e) => { + let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0); + errors.push(Error::Toml(e.to_string())); + + default(errors) + }, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/constants.rs b/src/tools/clippy/clippy_lints/src/utils/constants.rs new file mode 100644 index 0000000000000..522932f054d89 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/constants.rs @@ -0,0 +1,13 @@ +//! This module contains some useful constants. + +#![deny(clippy::missing_docs_in_private_items)] + +/// List of the built-in types names. +/// +/// See also [the reference][reference-types] for a list of such types. +/// +/// [reference-types]: https://doc.rust-lang.org/reference/types.html +pub const BUILTIN_TYPES: &[&str] = &[ + "i8", "u8", "i16", "u16", "i32", "u32", "i64", "u64", "i128", "u128", "isize", "usize", "f32", "f64", "bool", + "str", "char", +]; diff --git a/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs b/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs new file mode 100644 index 0000000000000..24a1bdf1883f6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs @@ -0,0 +1,217 @@ +//! Clippy wrappers around rustc's diagnostic functions. + +use rustc_errors::{Applicability, CodeSuggestion, DiagnosticBuilder, Substitution, SubstitutionPart, SuggestionStyle}; +use rustc_hir::HirId; +use rustc_lint::{LateContext, Lint, LintContext}; +use rustc_span::source_map::{MultiSpan, Span}; +use std::env; + +fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) { + if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() { + diag.help(&format!( + "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}", + &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| { + // extract just major + minor version and ignore patch versions + format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap()) + }), + lint.name_lower().replacen("clippy::", "", 1) + )); + } +} + +/// Emit a basic lint message with a `msg` and a `span`. +/// +/// This is the most primitive of our lint emission methods and can +/// be a good way to get a new lint started. +/// +/// Usually it's nicer to provide more context for lint messages. +/// Be sure the output is understandable when you use this method. +/// +/// # Example +/// +/// ```ignore +/// error: usage of mem::forget on Drop type +/// --> $DIR/mem_forget.rs:17:5 +/// | +/// 17 | std::mem::forget(seven); +/// | ^^^^^^^^^^^^^^^^^^^^^^^ +/// ``` +pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into, msg: &str) { + cx.struct_span_lint(lint, sp, |diag| { + let mut diag = diag.build(msg); + docs_link(&mut diag, lint); + diag.emit(); + }); +} + +/// Same as `span_lint` but with an extra `help` message. +/// +/// Use this if you want to provide some general help but +/// can't provide a specific machine applicable suggestion. +/// +/// The `help` message can be optionally attached to a `Span`. +/// +/// # Example +/// +/// ```ignore +/// error: constant division of 0.0 with 0.0 will always result in NaN +/// --> $DIR/zero_div_zero.rs:6:25 +/// | +/// 6 | let other_f64_nan = 0.0f64 / 0.0; +/// | ^^^^^^^^^^^^ +/// | +/// = help: Consider using `f64::NAN` if you would like a constant representing NaN +/// ``` +pub fn span_lint_and_help<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + help_span: Option, + help: &str, +) { + cx.struct_span_lint(lint, span, |diag| { + let mut diag = diag.build(msg); + if let Some(help_span) = help_span { + diag.span_help(help_span, help); + } else { + diag.help(help); + } + docs_link(&mut diag, lint); + diag.emit(); + }); +} + +/// Like `span_lint` but with a `note` section instead of a `help` message. +/// +/// The `note` message is presented separately from the main lint message +/// and is attached to a specific span: +/// +/// # Example +/// +/// ```ignore +/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. +/// --> $DIR/drop_forget_ref.rs:10:5 +/// | +/// 10 | forget(&SomeStruct); +/// | ^^^^^^^^^^^^^^^^^^^ +/// | +/// = note: `-D clippy::forget-ref` implied by `-D warnings` +/// note: argument has type &SomeStruct +/// --> $DIR/drop_forget_ref.rs:10:12 +/// | +/// 10 | forget(&SomeStruct); +/// | ^^^^^^^^^^^ +/// ``` +pub fn span_lint_and_note<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + note_span: Option, + note: &str, +) { + cx.struct_span_lint(lint, span, |diag| { + let mut diag = diag.build(msg); + if let Some(note_span) = note_span { + diag.span_note(note_span, note); + } else { + diag.note(note); + } + docs_link(&mut diag, lint); + diag.emit(); + }); +} + +/// Like `span_lint` but allows to add notes, help and suggestions using a closure. +/// +/// If you need to customize your lint output a lot, use this function. +pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F) +where + F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>), +{ + cx.struct_span_lint(lint, sp, |diag| { + let mut diag = diag.build(msg); + f(&mut diag); + docs_link(&mut diag, lint); + diag.emit(); + }); +} + +pub fn span_lint_hir(cx: &LateContext<'_, '_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) { + cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| { + let mut diag = diag.build(msg); + docs_link(&mut diag, lint); + diag.emit(); + }); +} + +pub fn span_lint_hir_and_then( + cx: &LateContext<'_, '_>, + lint: &'static Lint, + hir_id: HirId, + sp: Span, + msg: &str, + f: impl FnOnce(&mut DiagnosticBuilder<'_>), +) { + cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| { + let mut diag = diag.build(msg); + f(&mut diag); + docs_link(&mut diag, lint); + diag.emit(); + }); +} + +/// Add a span lint with a suggestion on how to fix it. +/// +/// These suggestions can be parsed by rustfix to allow it to automatically fix your code. +/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x > +/// 2)"`. +/// +/// ```ignore +/// error: This `.fold` can be more succinctly expressed as `.any` +/// --> $DIR/methods.rs:390:13 +/// | +/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` +/// | +/// = note: `-D fold-any` implied by `-D warnings` +/// ``` +#[allow(clippy::collapsible_span_lint_calls)] +pub fn span_lint_and_sugg<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + sp: Span, + msg: &str, + help: &str, + sugg: String, + applicability: Applicability, +) { + span_lint_and_then(cx, lint, sp, msg, |diag| { + diag.span_suggestion(sp, help, sugg, applicability); + }); +} + +/// Create a suggestion made from several `span → replacement`. +/// +/// Note: in the JSON format (used by `compiletest_rs`), the help message will +/// appear once per +/// replacement. In human-readable format though, it only appears once before +/// the whole suggestion. +pub fn multispan_sugg(diag: &mut DiagnosticBuilder<'_>, help_msg: String, sugg: I) +where + I: IntoIterator, +{ + let sugg = CodeSuggestion { + substitutions: vec![Substitution { + parts: sugg + .into_iter() + .map(|(span, snippet)| SubstitutionPart { snippet, span }) + .collect(), + }], + msg: help_msg, + style: SuggestionStyle::ShowCode, + applicability: Applicability::Unspecified, + }; + diag.suggestions.push(sugg); +} diff --git a/src/tools/clippy/clippy_lints/src/utils/higher.rs b/src/tools/clippy/clippy_lints/src/utils/higher.rs new file mode 100644 index 0000000000000..33fba7df8d336 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/higher.rs @@ -0,0 +1,293 @@ +//! This module contains functions for retrieve the original AST from lowered +//! `hir`. + +#![deny(clippy::missing_docs_in_private_items)] + +use crate::utils::{is_expn_of, match_def_path, match_qpath, paths}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; + +/// Converts a hir binary operator to the corresponding `ast` type. +#[must_use] +pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind { + match op { + hir::BinOpKind::Eq => ast::BinOpKind::Eq, + hir::BinOpKind::Ge => ast::BinOpKind::Ge, + hir::BinOpKind::Gt => ast::BinOpKind::Gt, + hir::BinOpKind::Le => ast::BinOpKind::Le, + hir::BinOpKind::Lt => ast::BinOpKind::Lt, + hir::BinOpKind::Ne => ast::BinOpKind::Ne, + hir::BinOpKind::Or => ast::BinOpKind::Or, + hir::BinOpKind::Add => ast::BinOpKind::Add, + hir::BinOpKind::And => ast::BinOpKind::And, + hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd, + hir::BinOpKind::BitOr => ast::BinOpKind::BitOr, + hir::BinOpKind::BitXor => ast::BinOpKind::BitXor, + hir::BinOpKind::Div => ast::BinOpKind::Div, + hir::BinOpKind::Mul => ast::BinOpKind::Mul, + hir::BinOpKind::Rem => ast::BinOpKind::Rem, + hir::BinOpKind::Shl => ast::BinOpKind::Shl, + hir::BinOpKind::Shr => ast::BinOpKind::Shr, + hir::BinOpKind::Sub => ast::BinOpKind::Sub, + } +} + +/// Represent a range akin to `ast::ExprKind::Range`. +#[derive(Debug, Copy, Clone)] +pub struct Range<'a> { + /// The lower bound of the range, or `None` for ranges such as `..X`. + pub start: Option<&'a hir::Expr<'a>>, + /// The upper bound of the range, or `None` for ranges such as `X..`. + pub end: Option<&'a hir::Expr<'a>>, + /// Whether the interval is open or closed. + pub limits: ast::RangeLimits, +} + +/// Higher a `hir` range to something similar to `ast::ExprKind::Range`. +pub fn range<'a, 'b, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'b hir::Expr<'_>) -> Option> { + /// Finds the field named `name` in the field. Always return `Some` for + /// convenience. + fn get_field<'c>(name: &str, fields: &'c [hir::Field<'_>]) -> Option<&'c hir::Expr<'c>> { + let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr; + + Some(expr) + } + + let def_path = match cx.tables.expr_ty(expr).kind { + ty::Adt(def, _) => cx.tcx.def_path(def.did), + _ => return None, + }; + + // sanity checks for std::ops::RangeXXXX + if def_path.data.len() != 3 { + return None; + } + if def_path.data.get(0)?.data.as_symbol() != sym!(ops) { + return None; + } + if def_path.data.get(1)?.data.as_symbol() != sym!(range) { + return None; + } + let type_name = def_path.data.get(2)?.data.as_symbol(); + let range_types = [ + "RangeFrom", + "RangeFull", + "RangeInclusive", + "Range", + "RangeTo", + "RangeToInclusive", + ]; + if !range_types.contains(&&*type_name.as_str()) { + return None; + } + + // The range syntax is expanded to literal paths starting with `core` or `std` + // depending on + // `#[no_std]`. Testing both instead of resolving the paths. + + match expr.kind { + hir::ExprKind::Path(ref path) => { + if match_qpath(path, &paths::RANGE_FULL_STD) || match_qpath(path, &paths::RANGE_FULL) { + Some(Range { + start: None, + end: None, + limits: ast::RangeLimits::HalfOpen, + }) + } else { + None + } + }, + hir::ExprKind::Call(ref path, ref args) => { + if let hir::ExprKind::Path(ref path) = path.kind { + if match_qpath(path, &paths::RANGE_INCLUSIVE_STD_NEW) || match_qpath(path, &paths::RANGE_INCLUSIVE_NEW) + { + Some(Range { + start: Some(&args[0]), + end: Some(&args[1]), + limits: ast::RangeLimits::Closed, + }) + } else { + None + } + } else { + None + } + }, + hir::ExprKind::Struct(ref path, ref fields, None) => { + if match_qpath(path, &paths::RANGE_FROM_STD) || match_qpath(path, &paths::RANGE_FROM) { + Some(Range { + start: Some(get_field("start", fields)?), + end: None, + limits: ast::RangeLimits::HalfOpen, + }) + } else if match_qpath(path, &paths::RANGE_STD) || match_qpath(path, &paths::RANGE) { + Some(Range { + start: Some(get_field("start", fields)?), + end: Some(get_field("end", fields)?), + limits: ast::RangeLimits::HalfOpen, + }) + } else if match_qpath(path, &paths::RANGE_TO_INCLUSIVE_STD) || match_qpath(path, &paths::RANGE_TO_INCLUSIVE) + { + Some(Range { + start: None, + end: Some(get_field("end", fields)?), + limits: ast::RangeLimits::Closed, + }) + } else if match_qpath(path, &paths::RANGE_TO_STD) || match_qpath(path, &paths::RANGE_TO) { + Some(Range { + start: None, + end: Some(get_field("end", fields)?), + limits: ast::RangeLimits::HalfOpen, + }) + } else { + None + } + }, + _ => None, + } +} + +/// Checks if a `let` statement is from a `for` loop desugaring. +pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool { + // This will detect plain for-loops without an actual variable binding: + // + // ``` + // for x in some_vec { + // // do stuff + // } + // ``` + if_chain! { + if let Some(ref expr) = local.init; + if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind; + then { + return true; + } + } + + // This detects a variable binding in for loop to avoid `let_unit_value` + // lint (see issue #1964). + // + // ``` + // for _ in vec![()] { + // // anything + // } + // ``` + if let hir::LocalSource::ForLoopDesugar = local.source { + return true; + } + + false +} + +/// Recover the essential nodes of a desugared for loop: +/// `for pat in arg { body }` becomes `(pat, arg, body)`. +pub fn for_loop<'tcx>( + expr: &'tcx hir::Expr<'tcx>, +) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> { + if_chain! { + if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind; + if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind; + if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none(); + if let hir::ExprKind::Loop(ref block, _, _) = arms[0].body.kind; + if block.expr.is_none(); + if let [ _, _, ref let_stmt, ref body ] = *block.stmts; + if let hir::StmtKind::Local(ref local) = let_stmt.kind; + if let hir::StmtKind::Expr(ref expr) = body.kind; + then { + return Some((&*local.pat, &iterargs[0], expr)); + } + } + None +} + +/// Recover the essential nodes of a desugared while loop: +/// `while cond { body }` becomes `(cond, body)`. +pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> { + if_chain! { + if let hir::ExprKind::Loop(block, _, hir::LoopSource::While) = &expr.kind; + if let hir::Block { expr: Some(expr), .. } = &**block; + if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind; + if let hir::ExprKind::DropTemps(cond) = &cond.kind; + if let [arm, ..] = &arms[..]; + if let hir::Arm { body, .. } = arm; + then { + return Some((cond, body)); + } + } + None +} + +/// Recover the essential nodes of a desugared if block +/// `if cond { then } else { els }` becomes `(cond, then, Some(els))` +pub fn if_block<'tcx>( + expr: &'tcx hir::Expr<'tcx>, +) -> Option<( + &'tcx hir::Expr<'tcx>, + &'tcx hir::Expr<'tcx>, + Option<&'tcx hir::Expr<'tcx>>, +)> { + if let hir::ExprKind::Match(ref cond, ref arms, hir::MatchSource::IfDesugar { contains_else_clause }) = expr.kind { + let cond = if let hir::ExprKind::DropTemps(ref cond) = cond.kind { + cond + } else { + panic!("If block desugar must contain DropTemps"); + }; + let then = &arms[0].body; + let els = if contains_else_clause { + Some(&*arms[1].body) + } else { + None + }; + Some((cond, then, els)) + } else { + None + } +} + +/// Represent the pre-expansion arguments of a `vec!` invocation. +pub enum VecArgs<'a> { + /// `vec![elem; len]` + Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>), + /// `vec![a, b, c]` + Vec(&'a [hir::Expr<'a>]), +} + +/// Returns the arguments of the `vec!` macro if this expression was expanded +/// from `vec!`. +pub fn vec_macro<'e>(cx: &LateContext<'_, '_>, expr: &'e hir::Expr<'_>) -> Option> { + if_chain! { + if let hir::ExprKind::Call(ref fun, ref args) = expr.kind; + if let hir::ExprKind::Path(ref qpath) = fun.kind; + if is_expn_of(fun.span, "vec").is_some(); + if let Some(fun_def_id) = cx.tables.qpath_res(qpath, fun.hir_id).opt_def_id(); + then { + return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 { + // `vec![elem; size]` case + Some(VecArgs::Repeat(&args[0], &args[1])) + } + else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 { + // `vec![a, b, c]` case + if_chain! { + if let hir::ExprKind::Box(ref boxed) = args[0].kind; + if let hir::ExprKind::Array(ref args) = boxed.kind; + then { + return Some(VecArgs::Vec(&*args)); + } + } + + None + } + else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() { + Some(VecArgs::Vec(&[])) + } + else { + None + }; + } + } + + None +} diff --git a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs new file mode 100644 index 0000000000000..02b721fd378ff --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs @@ -0,0 +1,694 @@ +use crate::consts::{constant_context, constant_simple}; +use crate::utils::differing_macro_contexts; +use rustc_ast::ast::Name; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_hir::{ + BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FnRetTy, GenericArg, + GenericArgs, Guard, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, Ty, + TyKind, TypeBinding, +}; +use rustc_lint::LateContext; +use rustc_middle::ich::StableHashingContextProvider; +use rustc_middle::ty::TypeckTables; +use std::hash::Hash; + +/// Type used to check whether two ast are the same. This is different from the +/// operator +/// `==` on ast types as this operator would compare true equality with ID and +/// span. +/// +/// Note that some expressions kinds are not considered but could be added. +pub struct SpanlessEq<'a, 'tcx> { + /// Context used to evaluate constant expressions. + cx: &'a LateContext<'a, 'tcx>, + tables: &'a TypeckTables<'tcx>, + /// If is true, never consider as equal expressions containing function + /// calls. + ignore_fn: bool, +} + +impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { + pub fn new(cx: &'a LateContext<'a, 'tcx>) -> Self { + Self { + cx, + tables: cx.tables, + ignore_fn: false, + } + } + + pub fn ignore_fn(self) -> Self { + Self { + cx: self.cx, + tables: self.cx.tables, + ignore_fn: true, + } + } + + /// Checks whether two statements are the same. + pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { + match (&left.kind, &right.kind) { + (&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => { + self.eq_pat(&l.pat, &r.pat) + && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) + && both(&l.init, &r.init, |l, r| self.eq_expr(l, r)) + }, + (&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => { + self.eq_expr(l, r) + }, + _ => false, + } + } + + /// Checks whether two blocks are the same. + pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { + over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r)) + && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r)) + } + + #[allow(clippy::similar_names)] + pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool { + if self.ignore_fn && differing_macro_contexts(left.span, right.span) { + return false; + } + + if let (Some(l), Some(r)) = ( + constant_simple(self.cx, self.tables, left), + constant_simple(self.cx, self.tables, right), + ) { + if l == r { + return true; + } + } + + match (&left.kind, &right.kind) { + (&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => { + lb == rb && l_mut == r_mut && self.eq_expr(le, re) + }, + (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => { + both(&li.label, &ri.label, |l, r| l.ident.as_str() == r.ident.as_str()) + }, + (&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => { + self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + }, + (&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => { + lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + }, + (&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r), + (&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => { + l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| { + l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + }) + }, + (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => { + both(&li.label, &ri.label, |l, r| l.ident.as_str() == r.ident.as_str()) + && both(le, re, |l, r| self.eq_expr(l, r)) + }, + (&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r), + (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => { + !self.ignore_fn && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args) + }, + (&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt)) + | (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => { + self.eq_expr(lx, rx) && self.eq_ty(lt, rt) + }, + (&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => { + l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp) + }, + (&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => { + self.eq_expr(la, ra) && self.eq_expr(li, ri) + }, + (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node, + (&ExprKind::Loop(ref lb, ref ll, ref lls), &ExprKind::Loop(ref rb, ref rl, ref rls)) => { + lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.as_str() == r.ident.as_str()) + }, + (&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => { + ls == rs + && self.eq_expr(le, re) + && over(la, ra, |l, r| { + self.eq_expr(&l.body, &r.body) + && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r)) + && self.eq_pat(&l.pat, &r.pat) + }) + }, + (&ExprKind::MethodCall(l_path, _, l_args), &ExprKind::MethodCall(r_path, _, r_args)) => { + !self.ignore_fn && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args) + }, + (&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => { + let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(ll_id.body)); + let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value); + let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(rl_id.body)); + let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value); + + self.eq_expr(le, re) && ll == rl + }, + (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)), + (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r), + (&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => { + self.eq_qpath(l_path, r_path) + && both(lo, ro, |l, r| self.eq_expr(l, r)) + && over(lf, rf, |l, r| self.eq_field(l, r)) + }, + (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup), + (&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re), + (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r), + (&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re), + _ => false, + } + } + + fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool { + over(left, right, |l, r| self.eq_expr(l, r)) + } + + fn eq_field(&mut self, left: &Field<'_>, right: &Field<'_>) -> bool { + left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr) + } + + fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool { + match (left, right) { + (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r), + } + } + + fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool { + match (left, right) { + (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt), + (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty), + _ => false, + } + } + + fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool { + left.name == right.name + } + + /// Checks whether two patterns are the same. + pub fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool { + match (&left.kind, &right.kind) { + (&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r), + (&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => { + self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs + }, + (&PatKind::Binding(ref lb, .., ref li, ref lp), &PatKind::Binding(ref rb, .., ref ri, ref rp)) => { + lb == rb && li.name.as_str() == ri.name.as_str() && both(lp, rp, |l, r| self.eq_pat(l, r)) + }, + (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r), + (&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r), + (&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => { + ls == rs && over(l, r, |l, r| self.eq_pat(l, r)) + }, + (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => { + both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri) + }, + (&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re), + (&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => { + over(ls, rs, |l, r| self.eq_pat(l, r)) + && over(le, re, |l, r| self.eq_pat(l, r)) + && both(li, ri, |l, r| self.eq_pat(l, r)) + }, + (&PatKind::Wild, &PatKind::Wild) => true, + _ => false, + } + } + + #[allow(clippy::similar_names)] + fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool { + match (left, right) { + (&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => { + both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath) + }, + (&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => { + self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg) + }, + _ => false, + } + } + + fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool { + left.is_global() == right.is_global() + && over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r)) + } + + fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool { + if !(left.parenthesized || right.parenthesized) { + over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work + && over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r)) + } else if left.parenthesized && right.parenthesized { + over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r)) + && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| { + self.eq_ty(l, r) + }) + } else { + false + } + } + + pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool { + left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r)) + } + + pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool { + // The == of idents doesn't work with different contexts, + // we have to be explicit about hygiene + if left.ident.as_str() != right.ident.as_str() { + return false; + } + match (&left.args, &right.args) { + (&None, &None) => true, + (&Some(ref l), &Some(ref r)) => self.eq_path_parameters(l, r), + _ => false, + } + } + + pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool { + self.eq_ty_kind(&left.kind, &right.kind) + } + + #[allow(clippy::similar_names)] + pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool { + match (left, right) { + (&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec), + (&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => { + let full_table = self.tables; + + let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(ll_id.body)); + self.tables = self.cx.tcx.body_tables(ll_id.body); + let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value); + + let mut celcx = constant_context(self.cx, self.cx.tcx.body_tables(rl_id.body)); + self.tables = self.cx.tcx.body_tables(rl_id.body); + let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value); + + let eq_ty = self.eq_ty(lt, rt); + self.tables = full_table; + eq_ty && ll == rl + }, + (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => { + l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty) + }, + (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => { + l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty) + }, + (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r), + (&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)), + (&TyKind::Infer, &TyKind::Infer) => true, + _ => false, + } + } + + fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool { + left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty()) + } +} + +fn swap_binop<'a>( + binop: BinOpKind, + lhs: &'a Expr<'a>, + rhs: &'a Expr<'a>, +) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> { + match binop { + BinOpKind::Add + | BinOpKind::Mul + | BinOpKind::Eq + | BinOpKind::Ne + | BinOpKind::BitAnd + | BinOpKind::BitXor + | BinOpKind::BitOr => Some((binop, rhs, lhs)), + BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)), + BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)), + BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)), + BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)), + BinOpKind::Shl + | BinOpKind::Shr + | BinOpKind::Rem + | BinOpKind::Sub + | BinOpKind::Div + | BinOpKind::And + | BinOpKind::Or => None, + } +} + +/// Checks if the two `Option`s are both `None` or some equal values as per +/// `eq_fn`. +fn both(l: &Option, r: &Option, mut eq_fn: F) -> bool +where + F: FnMut(&X, &X) -> bool, +{ + l.as_ref() + .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y))) +} + +/// Checks if two slices are equal as per `eq_fn`. +fn over(left: &[X], right: &[X], mut eq_fn: F) -> bool +where + F: FnMut(&X, &X) -> bool, +{ + left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y)) +} + +/// Type used to hash an ast element. This is different from the `Hash` trait +/// on ast types as this +/// trait would consider IDs and spans. +/// +/// All expressions kind are hashed, but some might have a weaker hash. +pub struct SpanlessHash<'a, 'tcx> { + /// Context used to evaluate constant expressions. + cx: &'a LateContext<'a, 'tcx>, + tables: &'a TypeckTables<'tcx>, + s: StableHasher, +} + +impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { + pub fn new(cx: &'a LateContext<'a, 'tcx>, tables: &'a TypeckTables<'tcx>) -> Self { + Self { + cx, + tables, + s: StableHasher::new(), + } + } + + pub fn finish(self) -> u64 { + self.s.finish() + } + + pub fn hash_block(&mut self, b: &Block<'_>) { + for s in b.stmts { + self.hash_stmt(s); + } + + if let Some(ref e) = b.expr { + self.hash_expr(e); + } + + match b.rules { + BlockCheckMode::DefaultBlock => 0, + BlockCheckMode::UnsafeBlock(_) => 1, + BlockCheckMode::PushUnsafeBlock(_) => 2, + BlockCheckMode::PopUnsafeBlock(_) => 3, + } + .hash(&mut self.s); + } + + #[allow(clippy::many_single_char_names, clippy::too_many_lines)] + pub fn hash_expr(&mut self, e: &Expr<'_>) { + let simple_const = constant_simple(self.cx, self.tables, e); + + // const hashing may result in the same hash as some unrelated node, so add a sort of + // discriminant depending on which path we're choosing next + simple_const.is_some().hash(&mut self.s); + + if let Some(e) = simple_const { + return e.hash(&mut self.s); + } + + std::mem::discriminant(&e.kind).hash(&mut self.s); + + match e.kind { + ExprKind::AddrOf(kind, m, ref e) => { + match kind { + BorrowKind::Ref => 0, + BorrowKind::Raw => 1, + } + .hash(&mut self.s); + m.hash(&mut self.s); + self.hash_expr(e); + }, + ExprKind::Continue(i) => { + if let Some(i) = i.label { + self.hash_name(i.ident.name); + } + }, + ExprKind::Assign(ref l, ref r, _) => { + self.hash_expr(l); + self.hash_expr(r); + }, + ExprKind::AssignOp(ref o, ref l, ref r) => { + o.node + .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s); + self.hash_expr(l); + self.hash_expr(r); + }, + ExprKind::Block(ref b, _) => { + self.hash_block(b); + }, + ExprKind::Binary(op, ref l, ref r) => { + op.node + .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s); + self.hash_expr(l); + self.hash_expr(r); + }, + ExprKind::Break(i, ref j) => { + if let Some(i) = i.label { + self.hash_name(i.ident.name); + } + if let Some(ref j) = *j { + self.hash_expr(&*j); + } + }, + ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => { + self.hash_expr(e); + }, + ExprKind::Call(ref fun, args) => { + self.hash_expr(fun); + self.hash_exprs(args); + }, + ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => { + self.hash_expr(e); + self.hash_ty(ty); + }, + ExprKind::Closure(cap, _, eid, _, _) => { + match cap { + CaptureBy::Value => 0, + CaptureBy::Ref => 1, + } + .hash(&mut self.s); + // closures inherit TypeckTables + self.hash_expr(&self.cx.tcx.hir().body(eid).value); + }, + ExprKind::Field(ref e, ref f) => { + self.hash_expr(e); + self.hash_name(f.name); + }, + ExprKind::Index(ref a, ref i) => { + self.hash_expr(a); + self.hash_expr(i); + }, + ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {}, + ExprKind::Lit(ref l) => { + l.node.hash(&mut self.s); + }, + ExprKind::Loop(ref b, ref i, _) => { + self.hash_block(b); + if let Some(i) = *i { + self.hash_name(i.ident.name); + } + }, + ExprKind::Match(ref e, arms, ref s) => { + self.hash_expr(e); + + for arm in arms { + // TODO: arm.pat? + if let Some(ref e) = arm.guard { + self.hash_guard(e); + } + self.hash_expr(&arm.body); + } + + s.hash(&mut self.s); + }, + ExprKind::MethodCall(ref path, ref _tys, args) => { + self.hash_name(path.ident.name); + self.hash_exprs(args); + }, + ExprKind::Repeat(ref e, ref l_id) => { + self.hash_expr(e); + self.hash_body(l_id.body); + }, + ExprKind::Ret(ref e) => { + if let Some(ref e) = *e { + self.hash_expr(e); + } + }, + ExprKind::Path(ref qpath) => { + self.hash_qpath(qpath); + }, + ExprKind::Struct(ref path, fields, ref expr) => { + self.hash_qpath(path); + + for f in fields { + self.hash_name(f.ident.name); + self.hash_expr(&f.expr); + } + + if let Some(ref e) = *expr { + self.hash_expr(e); + } + }, + ExprKind::Tup(tup) => { + self.hash_exprs(tup); + }, + ExprKind::Array(v) => { + self.hash_exprs(v); + }, + ExprKind::Unary(lop, ref le) => { + lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s); + self.hash_expr(le); + }, + } + } + + pub fn hash_exprs(&mut self, e: &[Expr<'_>]) { + for e in e { + self.hash_expr(e); + } + } + + pub fn hash_name(&mut self, n: Name) { + n.as_str().hash(&mut self.s); + } + + pub fn hash_qpath(&mut self, p: &QPath<'_>) { + match *p { + QPath::Resolved(_, ref path) => { + self.hash_path(path); + }, + QPath::TypeRelative(_, ref path) => { + self.hash_name(path.ident.name); + }, + } + // self.cx.tables.qpath_res(p, id).hash(&mut self.s); + } + + pub fn hash_path(&mut self, p: &Path<'_>) { + p.is_global().hash(&mut self.s); + for p in p.segments { + self.hash_name(p.ident.name); + } + } + + pub fn hash_stmt(&mut self, b: &Stmt<'_>) { + std::mem::discriminant(&b.kind).hash(&mut self.s); + + match &b.kind { + StmtKind::Local(local) => { + if let Some(ref init) = local.init { + self.hash_expr(init); + } + }, + StmtKind::Item(..) => {}, + StmtKind::Expr(expr) | StmtKind::Semi(expr) => { + self.hash_expr(expr); + }, + } + } + + pub fn hash_guard(&mut self, g: &Guard<'_>) { + match g { + Guard::If(ref expr) => { + self.hash_expr(expr); + }, + } + } + + pub fn hash_lifetime(&mut self, lifetime: &Lifetime) { + std::mem::discriminant(&lifetime.name).hash(&mut self.s); + if let LifetimeName::Param(ref name) = lifetime.name { + std::mem::discriminant(name).hash(&mut self.s); + match name { + ParamName::Plain(ref ident) => { + ident.name.hash(&mut self.s); + }, + ParamName::Fresh(ref size) => { + size.hash(&mut self.s); + }, + ParamName::Error => {}, + } + } + } + + pub fn hash_ty(&mut self, ty: &Ty<'_>) { + self.hash_tykind(&ty.kind); + } + + pub fn hash_tykind(&mut self, ty: &TyKind<'_>) { + std::mem::discriminant(ty).hash(&mut self.s); + match ty { + TyKind::Slice(ty) => { + self.hash_ty(ty); + }, + TyKind::Array(ty, anon_const) => { + self.hash_ty(ty); + self.hash_body(anon_const.body); + }, + TyKind::Ptr(mut_ty) => { + self.hash_ty(&mut_ty.ty); + mut_ty.mutbl.hash(&mut self.s); + }, + TyKind::Rptr(lifetime, mut_ty) => { + self.hash_lifetime(lifetime); + self.hash_ty(&mut_ty.ty); + mut_ty.mutbl.hash(&mut self.s); + }, + TyKind::BareFn(bfn) => { + bfn.unsafety.hash(&mut self.s); + bfn.abi.hash(&mut self.s); + for arg in bfn.decl.inputs { + self.hash_ty(&arg); + } + match bfn.decl.output { + FnRetTy::DefaultReturn(_) => { + ().hash(&mut self.s); + }, + FnRetTy::Return(ref ty) => { + self.hash_ty(ty); + }, + } + bfn.decl.c_variadic.hash(&mut self.s); + }, + TyKind::Tup(ty_list) => { + for ty in *ty_list { + self.hash_ty(ty); + } + }, + TyKind::Path(qpath) => match qpath { + QPath::Resolved(ref maybe_ty, ref path) => { + if let Some(ref ty) = maybe_ty { + self.hash_ty(ty); + } + for segment in path.segments { + segment.ident.name.hash(&mut self.s); + } + }, + QPath::TypeRelative(ref ty, ref segment) => { + self.hash_ty(ty); + segment.ident.name.hash(&mut self.s); + }, + }, + TyKind::Def(_, arg_list) => { + for arg in *arg_list { + match arg { + GenericArg::Lifetime(ref l) => self.hash_lifetime(l), + GenericArg::Type(ref ty) => self.hash_ty(&ty), + GenericArg::Const(ref ca) => self.hash_body(ca.value.body), + } + } + }, + TyKind::TraitObject(_, lifetime) => { + self.hash_lifetime(lifetime); + }, + TyKind::Typeof(anon_const) => { + self.hash_body(anon_const.body); + }, + TyKind::Err | TyKind::Infer | TyKind::Never => {}, + } + } + + pub fn hash_body(&mut self, body_id: BodyId) { + // swap out TypeckTables when hashing a body + let old_tables = self.tables; + self.tables = self.cx.tcx.body_tables(body_id); + self.hash_expr(&self.cx.tcx.hir().body(body_id).value); + self.tables = old_tables; + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/inspector.rs b/src/tools/clippy/clippy_lints/src/utils/inspector.rs new file mode 100644 index 0000000000000..7e8c61ba24a22 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/inspector.rs @@ -0,0 +1,525 @@ +//! checks for attributes + +use crate::utils::get_attr; +use rustc_ast::ast::Attribute; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::Session; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Dumps every ast/hir node which has the `#[clippy::dump]` + /// attribute + /// + /// **Example:** + /// ```rust,ignore + /// #[clippy::dump] + /// extern crate foo; + /// ``` + /// + /// prints + /// + /// ```text + /// item `foo` + /// visibility inherited from outer item + /// extern crate dylib source: "/path/to/foo.so" + /// ``` + pub DEEP_CODE_INSPECTION, + internal_warn, + "helper to dump info about code" +} + +declare_lint_pass!(DeepCodeInspector => [DEEP_CODE_INSPECTION]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DeepCodeInspector { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) { + if !has_attr(cx.sess(), &item.attrs) { + return; + } + print_item(cx, item); + } + + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'_>) { + if !has_attr(cx.sess(), &item.attrs) { + return; + } + println!("impl item `{}`", item.ident.name); + match item.vis.node { + hir::VisibilityKind::Public => println!("public"), + hir::VisibilityKind::Crate(_) => println!("visible crate wide"), + hir::VisibilityKind::Restricted { ref path, .. } => println!( + "visible in module `{}`", + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(path, false)) + ), + hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"), + } + if item.defaultness.is_default() { + println!("default"); + } + match item.kind { + hir::ImplItemKind::Const(_, body_id) => { + println!("associated constant"); + print_expr(cx, &cx.tcx.hir().body(body_id).value, 1); + }, + hir::ImplItemKind::Fn(..) => println!("method"), + hir::ImplItemKind::TyAlias(_) => println!("associated type"), + hir::ImplItemKind::OpaqueTy(_) => println!("existential type"), + } + } + // fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx + // hir::TraitItem) { + // if !has_attr(&item.attrs) { + // return; + // } + // } + // + // fn check_variant(&mut self, cx: &LateContext<'a, 'tcx>, var: &'tcx + // hir::Variant, _: + // &hir::Generics) { + // if !has_attr(&var.node.attrs) { + // return; + // } + // } + // + // fn check_struct_field(&mut self, cx: &LateContext<'a, 'tcx>, field: &'tcx + // hir::StructField) { + // if !has_attr(&field.attrs) { + // return; + // } + // } + // + + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if !has_attr(cx.sess(), &expr.attrs) { + return; + } + print_expr(cx, expr, 0); + } + + fn check_arm(&mut self, cx: &LateContext<'a, 'tcx>, arm: &'tcx hir::Arm<'_>) { + if !has_attr(cx.sess(), &arm.attrs) { + return; + } + print_pat(cx, &arm.pat, 1); + if let Some(ref guard) = arm.guard { + println!("guard:"); + print_guard(cx, guard, 1); + } + println!("body:"); + print_expr(cx, &arm.body, 1); + } + + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx hir::Stmt<'_>) { + if !has_attr(cx.sess(), stmt.kind.attrs()) { + return; + } + match stmt.kind { + hir::StmtKind::Local(ref local) => { + println!("local variable of type {}", cx.tables.node_type(local.hir_id)); + println!("pattern:"); + print_pat(cx, &local.pat, 0); + if let Some(ref e) = local.init { + println!("init expression:"); + print_expr(cx, e, 0); + } + }, + hir::StmtKind::Item(_) => println!("item decl"), + hir::StmtKind::Expr(ref e) | hir::StmtKind::Semi(ref e) => print_expr(cx, e, 0), + } + } + // fn check_foreign_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx + // hir::ForeignItem) { + // if !has_attr(&item.attrs) { + // return; + // } + // } + // +} + +fn has_attr(sess: &Session, attrs: &[Attribute]) -> bool { + get_attr(sess, attrs, "dump").count() > 0 +} + +#[allow(clippy::similar_names)] +#[allow(clippy::too_many_lines)] +fn print_expr(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, indent: usize) { + let ind = " ".repeat(indent); + println!("{}+", ind); + println!("{}ty: {}", ind, cx.tables.expr_ty(expr)); + println!("{}adjustments: {:?}", ind, cx.tables.adjustments().get(expr.hir_id)); + match expr.kind { + hir::ExprKind::Box(ref e) => { + println!("{}Box", ind); + print_expr(cx, e, indent + 1); + }, + hir::ExprKind::Array(v) => { + println!("{}Array", ind); + for e in v { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Call(ref func, args) => { + println!("{}Call", ind); + println!("{}function:", ind); + print_expr(cx, func, indent + 1); + println!("{}arguments:", ind); + for arg in args { + print_expr(cx, arg, indent + 1); + } + }, + hir::ExprKind::MethodCall(ref path, _, args) => { + println!("{}MethodCall", ind); + println!("{}method name: {}", ind, path.ident.name); + for arg in args { + print_expr(cx, arg, indent + 1); + } + }, + hir::ExprKind::Tup(v) => { + println!("{}Tup", ind); + for e in v { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Binary(op, ref lhs, ref rhs) => { + println!("{}Binary", ind); + println!("{}op: {:?}", ind, op.node); + println!("{}lhs:", ind); + print_expr(cx, lhs, indent + 1); + println!("{}rhs:", ind); + print_expr(cx, rhs, indent + 1); + }, + hir::ExprKind::Unary(op, ref inner) => { + println!("{}Unary", ind); + println!("{}op: {:?}", ind, op); + print_expr(cx, inner, indent + 1); + }, + hir::ExprKind::Lit(ref lit) => { + println!("{}Lit", ind); + println!("{}{:?}", ind, lit); + }, + hir::ExprKind::Cast(ref e, ref target) => { + println!("{}Cast", ind); + print_expr(cx, e, indent + 1); + println!("{}target type: {:?}", ind, target); + }, + hir::ExprKind::Type(ref e, ref target) => { + println!("{}Type", ind); + print_expr(cx, e, indent + 1); + println!("{}target type: {:?}", ind, target); + }, + hir::ExprKind::Loop(..) => { + println!("{}Loop", ind); + }, + hir::ExprKind::Match(ref cond, _, ref source) => { + println!("{}Match", ind); + println!("{}condition:", ind); + print_expr(cx, cond, indent + 1); + println!("{}source: {:?}", ind, source); + }, + hir::ExprKind::Closure(ref clause, _, _, _, _) => { + println!("{}Closure", ind); + println!("{}clause: {:?}", ind, clause); + }, + hir::ExprKind::Yield(ref sub, _) => { + println!("{}Yield", ind); + print_expr(cx, sub, indent + 1); + }, + hir::ExprKind::Block(_, _) => { + println!("{}Block", ind); + }, + hir::ExprKind::Assign(ref lhs, ref rhs, _) => { + println!("{}Assign", ind); + println!("{}lhs:", ind); + print_expr(cx, lhs, indent + 1); + println!("{}rhs:", ind); + print_expr(cx, rhs, indent + 1); + }, + hir::ExprKind::AssignOp(ref binop, ref lhs, ref rhs) => { + println!("{}AssignOp", ind); + println!("{}op: {:?}", ind, binop.node); + println!("{}lhs:", ind); + print_expr(cx, lhs, indent + 1); + println!("{}rhs:", ind); + print_expr(cx, rhs, indent + 1); + }, + hir::ExprKind::Field(ref e, ident) => { + println!("{}Field", ind); + println!("{}field name: {}", ind, ident.name); + println!("{}struct expr:", ind); + print_expr(cx, e, indent + 1); + }, + hir::ExprKind::Index(ref arr, ref idx) => { + println!("{}Index", ind); + println!("{}array expr:", ind); + print_expr(cx, arr, indent + 1); + println!("{}index expr:", ind); + print_expr(cx, idx, indent + 1); + }, + hir::ExprKind::Path(hir::QPath::Resolved(ref ty, ref path)) => { + println!("{}Resolved Path, {:?}", ind, ty); + println!("{}path: {:?}", ind, path); + }, + hir::ExprKind::Path(hir::QPath::TypeRelative(ref ty, ref seg)) => { + println!("{}Relative Path, {:?}", ind, ty); + println!("{}seg: {:?}", ind, seg); + }, + hir::ExprKind::AddrOf(kind, ref muta, ref e) => { + println!("{}AddrOf", ind); + println!("kind: {:?}", kind); + println!("mutability: {:?}", muta); + print_expr(cx, e, indent + 1); + }, + hir::ExprKind::Break(_, ref e) => { + println!("{}Break", ind); + if let Some(ref e) = *e { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Continue(_) => println!("{}Again", ind), + hir::ExprKind::Ret(ref e) => { + println!("{}Ret", ind); + if let Some(ref e) = *e { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::LlvmInlineAsm(ref asm) => { + let inputs = &asm.inputs_exprs; + let outputs = &asm.outputs_exprs; + println!("{}LlvmInlineAsm", ind); + println!("{}inputs:", ind); + for e in inputs.iter() { + print_expr(cx, e, indent + 1); + } + println!("{}outputs:", ind); + for e in outputs.iter() { + print_expr(cx, e, indent + 1); + } + }, + hir::ExprKind::Struct(ref path, fields, ref base) => { + println!("{}Struct", ind); + println!("{}path: {:?}", ind, path); + for field in fields { + println!("{}field \"{}\":", ind, field.ident.name); + print_expr(cx, &field.expr, indent + 1); + } + if let Some(ref base) = *base { + println!("{}base:", ind); + print_expr(cx, base, indent + 1); + } + }, + hir::ExprKind::Repeat(ref val, ref anon_const) => { + println!("{}Repeat", ind); + println!("{}value:", ind); + print_expr(cx, val, indent + 1); + println!("{}repeat count:", ind); + print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1); + }, + hir::ExprKind::Err => { + println!("{}Err", ind); + }, + hir::ExprKind::DropTemps(ref e) => { + println!("{}DropTemps", ind); + print_expr(cx, e, indent + 1); + }, + } +} + +fn print_item(cx: &LateContext<'_, '_>, item: &hir::Item<'_>) { + let did = cx.tcx.hir().local_def_id(item.hir_id); + println!("item `{}`", item.ident.name); + match item.vis.node { + hir::VisibilityKind::Public => println!("public"), + hir::VisibilityKind::Crate(_) => println!("visible crate wide"), + hir::VisibilityKind::Restricted { ref path, .. } => println!( + "visible in module `{}`", + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(path, false)) + ), + hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"), + } + match item.kind { + hir::ItemKind::ExternCrate(ref _renamed_from) => { + let def_id = cx.tcx.hir().local_def_id(item.hir_id); + if let Some(crate_id) = cx.tcx.extern_mod_stmt_cnum(def_id) { + let source = cx.tcx.used_crate_source(crate_id); + if let Some(ref src) = source.dylib { + println!("extern crate dylib source: {:?}", src.0); + } + if let Some(ref src) = source.rlib { + println!("extern crate rlib source: {:?}", src.0); + } + } else { + println!("weird extern crate without a crate id"); + } + }, + hir::ItemKind::Use(ref path, ref kind) => println!("{:?}, {:?}", path, kind), + hir::ItemKind::Static(..) => println!("static item of type {:#?}", cx.tcx.type_of(did)), + hir::ItemKind::Const(..) => println!("const item of type {:#?}", cx.tcx.type_of(did)), + hir::ItemKind::Fn(..) => { + let item_ty = cx.tcx.type_of(did); + println!("function of type {:#?}", item_ty); + }, + hir::ItemKind::Mod(..) => println!("module"), + hir::ItemKind::ForeignMod(ref fm) => println!("foreign module with abi: {}", fm.abi), + hir::ItemKind::GlobalAsm(ref asm) => println!("global asm: {:?}", asm), + hir::ItemKind::TyAlias(..) => { + println!("type alias for {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::OpaqueTy(..) => { + println!("existential type with real type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Enum(..) => { + println!("enum definition of type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Struct(..) => { + println!("struct definition of type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Union(..) => { + println!("union definition of type {:?}", cx.tcx.type_of(did)); + }, + hir::ItemKind::Trait(..) => { + println!("trait decl"); + if cx.tcx.trait_is_auto(did.to_def_id()) { + println!("trait is auto"); + } else { + println!("trait is not auto"); + } + }, + hir::ItemKind::TraitAlias(..) => { + println!("trait alias"); + }, + hir::ItemKind::Impl { + of_trait: Some(ref _trait_ref), + .. + } => { + println!("trait impl"); + }, + hir::ItemKind::Impl { of_trait: None, .. } => { + println!("impl"); + }, + } +} + +#[allow(clippy::similar_names)] +#[allow(clippy::too_many_lines)] +fn print_pat(cx: &LateContext<'_, '_>, pat: &hir::Pat<'_>, indent: usize) { + let ind = " ".repeat(indent); + println!("{}+", ind); + match pat.kind { + hir::PatKind::Wild => println!("{}Wild", ind), + hir::PatKind::Binding(ref mode, .., ident, ref inner) => { + println!("{}Binding", ind); + println!("{}mode: {:?}", ind, mode); + println!("{}name: {}", ind, ident.name); + if let Some(ref inner) = *inner { + println!("{}inner:", ind); + print_pat(cx, inner, indent + 1); + } + }, + hir::PatKind::Or(fields) => { + println!("{}Or", ind); + for field in fields { + print_pat(cx, field, indent + 1); + } + }, + hir::PatKind::Struct(ref path, fields, ignore) => { + println!("{}Struct", ind); + println!( + "{}name: {}", + ind, + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + ); + println!("{}ignore leftover fields: {}", ind, ignore); + println!("{}fields:", ind); + for field in fields { + println!("{} field name: {}", ind, field.ident.name); + if field.is_shorthand { + println!("{} in shorthand notation", ind); + } + print_pat(cx, &field.pat, indent + 1); + } + }, + hir::PatKind::TupleStruct(ref path, fields, opt_dots_position) => { + println!("{}TupleStruct", ind); + println!( + "{}path: {}", + ind, + rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + ); + if let Some(dot_position) = opt_dots_position { + println!("{}dot position: {}", ind, dot_position); + } + for field in fields { + print_pat(cx, field, indent + 1); + } + }, + hir::PatKind::Path(hir::QPath::Resolved(ref ty, ref path)) => { + println!("{}Resolved Path, {:?}", ind, ty); + println!("{}path: {:?}", ind, path); + }, + hir::PatKind::Path(hir::QPath::TypeRelative(ref ty, ref seg)) => { + println!("{}Relative Path, {:?}", ind, ty); + println!("{}seg: {:?}", ind, seg); + }, + hir::PatKind::Tuple(pats, opt_dots_position) => { + println!("{}Tuple", ind); + if let Some(dot_position) = opt_dots_position { + println!("{}dot position: {}", ind, dot_position); + } + for field in pats { + print_pat(cx, field, indent + 1); + } + }, + hir::PatKind::Box(ref inner) => { + println!("{}Box", ind); + print_pat(cx, inner, indent + 1); + }, + hir::PatKind::Ref(ref inner, ref muta) => { + println!("{}Ref", ind); + println!("{}mutability: {:?}", ind, muta); + print_pat(cx, inner, indent + 1); + }, + hir::PatKind::Lit(ref e) => { + println!("{}Lit", ind); + print_expr(cx, e, indent + 1); + }, + hir::PatKind::Range(ref l, ref r, ref range_end) => { + println!("{}Range", ind); + if let Some(expr) = l { + print_expr(cx, expr, indent + 1); + } + if let Some(expr) = r { + print_expr(cx, expr, indent + 1); + } + match *range_end { + hir::RangeEnd::Included => println!("{} end included", ind), + hir::RangeEnd::Excluded => println!("{} end excluded", ind), + } + }, + hir::PatKind::Slice(first_pats, ref range, last_pats) => { + println!("{}Slice [a, b, ..i, y, z]", ind); + println!("[a, b]:"); + for pat in first_pats { + print_pat(cx, pat, indent + 1); + } + println!("i:"); + if let Some(ref pat) = *range { + print_pat(cx, pat, indent + 1); + } + println!("[y, z]:"); + for pat in last_pats { + print_pat(cx, pat, indent + 1); + } + }, + } +} + +fn print_guard(cx: &LateContext<'_, '_>, guard: &hir::Guard<'_>, indent: usize) { + let ind = " ".repeat(indent); + println!("{}+", ind); + match guard { + hir::Guard::If(expr) => { + println!("{}If", ind); + print_expr(cx, expr, indent + 1); + }, + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs new file mode 100644 index 0000000000000..5bf9acdc5f7ce --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs @@ -0,0 +1,658 @@ +use crate::utils::SpanlessEq; +use crate::utils::{ + is_expn_of, match_def_path, match_qpath, match_type, method_calls, paths, run_lints, snippet, span_lint, + span_lint_and_help, span_lint_and_sugg, walk_ptrs_ty, +}; +use if_chain::if_chain; +use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, Name, NodeId}; +use rustc_ast::visit::FnKind; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::hir_id::CRATE_HIR_ID; +use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; +use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Path, StmtKind, Ty, TyKind}; +use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; +use rustc_span::source_map::{Span, Spanned}; +use rustc_span::symbol::SymbolStr; + +use std::borrow::{Borrow, Cow}; + +declare_clippy_lint! { + /// **What it does:** Checks for various things we like to keep tidy in clippy. + /// + /// **Why is this bad?** We like to pretend we're an example of tidy code. + /// + /// **Known problems:** None. + /// + /// **Example:** Wrong ordering of the util::paths constants. + pub CLIPPY_LINTS_INTERNAL, + internal, + "various things that will negatively affect your clippy experience" +} + +declare_clippy_lint! { + /// **What it does:** Ensures every lint is associated to a `LintPass`. + /// + /// **Why is this bad?** The compiler only knows lints via a `LintPass`. Without + /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not + /// know the name of the lint. + /// + /// **Known problems:** Only checks for lints associated using the + /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros. + /// + /// **Example:** + /// ```rust,ignore + /// declare_lint! { pub LINT_1, ... } + /// declare_lint! { pub LINT_2, ... } + /// declare_lint! { pub FORGOTTEN_LINT, ... } + /// // ... + /// declare_lint_pass!(Pass => [LINT_1, LINT_2]); + /// // missing FORGOTTEN_LINT + /// ``` + pub LINT_WITHOUT_LINT_PASS, + internal, + "declaring a lint without associating it in a LintPass" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `cx.span_lint*` and suggests to use the `utils::*` + /// variant of the function. + /// + /// **Why is this bad?** The `utils::*` variants also add a link to the Clippy documentation to the + /// warning/error messages. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// cx.span_lint(LINT_NAME, "message"); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// utils::span_lint(cx, LINT_NAME, "message"); + /// ``` + pub COMPILER_LINT_FUNCTIONS, + internal, + "usage of the lint functions of the compiler instead of the utils::* variant" +} + +declare_clippy_lint! { + /// **What it does:** Checks for calls to `cx.outer().expn_data()` and suggests to use + /// the `cx.outer_expn_data()` + /// + /// **Why is this bad?** `cx.outer_expn_data()` is faster and more concise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// expr.span.ctxt().outer().expn_data() + /// ``` + /// + /// Good: + /// ```rust,ignore + /// expr.span.ctxt().outer_expn_data() + /// ``` + pub OUTER_EXPN_EXPN_DATA, + internal, + "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`" +} + +declare_clippy_lint! { + /// **What it does:** Not an actual lint. This lint is only meant for testing our customized internal compiler + /// error message by calling `panic`. + /// + /// **Why is this bad?** ICE in large quantities can damage your teeth + /// + /// **Known problems:** None + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// 🍦🍦🍦🍦🍦 + /// ``` + pub PRODUCE_ICE, + internal, + "this message should not appear anywhere as we ICE before and don't emit the lint" +} + +declare_clippy_lint! { + /// **What it does:** Checks for cases of an auto-generated lint without an updated description, + /// i.e. `default lint description`. + /// + /// **Why is this bad?** Indicates that the lint is not finished. + /// + /// **Known problems:** None + /// + /// **Example:** + /// Bad: + /// ```rust,ignore + /// declare_lint! { pub COOL_LINT, nursery, "default lint description" } + /// ``` + /// + /// Good: + /// ```rust,ignore + /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" } + /// ``` + pub DEFAULT_LINT, + internal, + "found 'default lint description' in a lint declaration" +} + +declare_clippy_lint! { + /// **What it does:** Lints `span_lint_and_then` function calls, where the + /// closure argument has only one statement and that statement is a method + /// call to `span_suggestion`, `span_help`, `span_note` (using the same + /// span), `help` or `note`. + /// + /// These usages of `span_lint_and_then` should be replaced with one of the + /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or + /// `span_lint_and_note`. + /// + /// **Why is this bad?** Using the wrapper `span_lint_and_*` functions, is more + /// convenient, readable and less error prone. + /// + /// **Known problems:** None + /// + /// *Example:** + /// Bad: + /// ```rust,ignore + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.span_suggestion( + /// expr.span, + /// help_msg, + /// sugg.to_string(), + /// Applicability::MachineApplicable, + /// ); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.span_help(expr.span, help_msg); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.help(help_msg); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.span_note(expr.span, note_msg); + /// }); + /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { + /// diag.note(note_msg); + /// }); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// span_lint_and_sugg( + /// cx, + /// TEST_LINT, + /// expr.span, + /// lint_msg, + /// help_msg, + /// sugg.to_string(), + /// Applicability::MachineApplicable, + /// ); + /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg); + /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg); + /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg); + /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg); + /// ``` + pub COLLAPSIBLE_SPAN_LINT_CALLS, + internal, + "found collapsible `span_lint_and_then` calls" +} + +declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); + +impl EarlyLintPass for ClippyLintsInternal { + fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &AstCrate) { + if let Some(utils) = krate + .module + .items + .iter() + .find(|item| item.ident.name.as_str() == "utils") + { + if let ItemKind::Mod(ref utils_mod) = utils.kind { + if let Some(paths) = utils_mod.items.iter().find(|item| item.ident.name.as_str() == "paths") { + if let ItemKind::Mod(ref paths_mod) = paths.kind { + let mut last_name: Option = None; + for item in &*paths_mod.items { + let name = item.ident.as_str(); + if let Some(ref last_name) = last_name { + if **last_name > *name { + span_lint( + cx, + CLIPPY_LINTS_INTERNAL, + item.span, + "this constant should be before the previous constant due to lexical \ + ordering", + ); + } + } + last_name = Some(name); + } + } + } + } + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct LintWithoutLintPass { + declared_lints: FxHashMap, + registered_lints: FxHashSet, +} + +impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LintWithoutLintPass { + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) { + if !run_lints(cx, &[DEFAULT_LINT], item.hir_id) { + return; + } + + if let hir::ItemKind::Static(ref ty, Mutability::Not, body_id) = item.kind { + if is_lint_ref_type(cx, ty) { + let expr = &cx.tcx.hir().body(body_id).value; + if_chain! { + if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind; + if let ExprKind::Struct(_, ref fields, _) = inner_exp.kind; + let field = fields + .iter() + .find(|f| f.ident.as_str() == "desc") + .expect("lints must have a description field"); + if let ExprKind::Lit(Spanned { + node: LitKind::Str(ref sym, _), + .. + }) = field.expr.kind; + if sym.as_str() == "default lint description"; + + then { + span_lint( + cx, + DEFAULT_LINT, + item.span, + &format!("the lint `{}` has the default lint description", item.ident.name), + ); + } + } + self.declared_lints.insert(item.ident.name, item.span); + } + } else if is_expn_of(item.span, "impl_lint_pass").is_some() + || is_expn_of(item.span, "declare_lint_pass").is_some() + { + if let hir::ItemKind::Impl { + of_trait: None, + items: ref impl_item_refs, + .. + } = item.kind + { + let mut collector = LintCollector { + output: &mut self.registered_lints, + cx, + }; + let body_id = cx.tcx.hir().body_owned_by( + impl_item_refs + .iter() + .find(|iiref| iiref.ident.as_str() == "get_lints") + .expect("LintPass needs to implement get_lints") + .id + .hir_id, + ); + collector.visit_expr(&cx.tcx.hir().body(body_id).value); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'a, 'tcx>, _: &'tcx Crate<'_>) { + if !run_lints(cx, &[LINT_WITHOUT_LINT_PASS], CRATE_HIR_ID) { + return; + } + + for (lint_name, &lint_span) in &self.declared_lints { + // When using the `declare_tool_lint!` macro, the original `lint_span`'s + // file points to "". + // `compiletest-rs` thinks that's an error in a different file and + // just ignores it. This causes the test in compile-fail/lint_pass + // not able to capture the error. + // Therefore, we need to climb the macro expansion tree and find the + // actual span that invoked `declare_tool_lint!`: + let lint_span = lint_span.ctxt().outer_expn_data().call_site; + + if !self.registered_lints.contains(lint_name) { + span_lint( + cx, + LINT_WITHOUT_LINT_PASS, + lint_span, + &format!("the lint `{}` is not added to any `LintPass`", lint_name), + ); + } + } + } +} + +fn is_lint_ref_type<'tcx>(cx: &LateContext<'_, 'tcx>, ty: &Ty<'_>) -> bool { + if let TyKind::Rptr( + _, + MutTy { + ty: ref inner, + mutbl: Mutability::Not, + }, + ) = ty.kind + { + if let TyKind::Path(ref path) = inner.kind { + if let Res::Def(DefKind::Struct, def_id) = cx.tables.qpath_res(path, inner.hir_id) { + return match_def_path(cx, def_id, &paths::LINT); + } + } + } + + false +} + +struct LintCollector<'a, 'tcx> { + output: &'a mut FxHashSet, + cx: &'a LateContext<'a, 'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_path(&mut self, path: &'tcx Path<'_>, _: HirId) { + if path.segments.len() == 1 { + self.output.insert(path.segments[0].ident.name); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +#[derive(Clone, Default)] +pub struct CompilerLintFunctions { + map: FxHashMap<&'static str, &'static str>, +} + +impl CompilerLintFunctions { + #[must_use] + pub fn new() -> Self { + let mut map = FxHashMap::default(); + map.insert("span_lint", "utils::span_lint"); + map.insert("struct_span_lint", "utils::span_lint"); + map.insert("lint", "utils::span_lint"); + map.insert("span_lint_note", "utils::span_lint_and_note"); + map.insert("span_lint_help", "utils::span_lint_and_help"); + Self { map } + } +} + +impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CompilerLintFunctions { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if !run_lints(cx, &[COMPILER_LINT_FUNCTIONS], expr.hir_id) { + return; + } + + if_chain! { + if let ExprKind::MethodCall(ref path, _, ref args) = expr.kind; + let fn_name = path.ident; + if let Some(sugg) = self.map.get(&*fn_name.as_str()); + let ty = walk_ptrs_ty(cx.tables.expr_ty(&args[0])); + if match_type(cx, ty, &paths::EARLY_CONTEXT) + || match_type(cx, ty, &paths::LATE_CONTEXT); + then { + span_lint_and_help( + cx, + COMPILER_LINT_FUNCTIONS, + path.ident.span, + "usage of a compiler lint function", + None, + &format!("please use the Clippy variant of this function: `{}`", sugg), + ); + } + } + } +} + +declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for OuterExpnDataPass { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if !run_lints(cx, &[OUTER_EXPN_EXPN_DATA], expr.hir_id) { + return; + } + + let (method_names, arg_lists, spans) = method_calls(expr, 2); + let method_names: Vec = method_names.iter().map(|s| s.as_str()).collect(); + let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect(); + if_chain! { + if let ["expn_data", "outer_expn"] = method_names.as_slice(); + let args = arg_lists[1]; + if args.len() == 1; + let self_arg = &args[0]; + let self_ty = walk_ptrs_ty(cx.tables.expr_ty(self_arg)); + if match_type(cx, self_ty, &paths::SYNTAX_CONTEXT); + then { + span_lint_and_sugg( + cx, + OUTER_EXPN_EXPN_DATA, + spans[1].with_hi(expr.span.hi()), + "usage of `outer_expn().expn_data()`", + "try", + "outer_expn_data()".to_string(), + Applicability::MachineApplicable, + ); + } + } + } +} + +declare_lint_pass!(ProduceIce => [PRODUCE_ICE]); + +impl EarlyLintPass for ProduceIce { + fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { + if is_trigger_fn(fn_kind) { + panic!("Would you like some help with that?"); + } + } +} + +fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool { + match fn_kind { + FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy", + FnKind::Closure(..) => false, + } +} + +declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CollapsibleCalls { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if !run_lints(cx, &[COLLAPSIBLE_SPAN_LINT_CALLS], expr.hir_id) { + return; + } + + if_chain! { + if let ExprKind::Call(ref func, ref and_then_args) = expr.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["span_lint_and_then"]); + if and_then_args.len() == 5; + if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind; + let body = cx.tcx.hir().body(*body_id); + if let ExprKind::Block(block, _) = &body.value.kind; + let stmts = &block.stmts; + if stmts.len() == 1 && block.expr.is_none(); + if let StmtKind::Semi(only_expr) = &stmts[0].kind; + if let ExprKind::MethodCall(ref ps, _, ref span_call_args) = &only_expr.kind; + let and_then_snippets = get_and_then_snippets(cx, and_then_args); + let mut sle = SpanlessEq::new(cx).ignore_fn(); + then { + match &*ps.ident.as_str() { + "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { + suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args)); + }, + "span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { + let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#); + suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true); + }, + "span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { + let note_snippet = snippet(cx, span_call_args[2].span, r#""...""#); + suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true); + }, + "help" => { + let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); + suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false); + } + "note" => { + let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#); + suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false); + } + _ => (), + } + } + } + } +} + +struct AndThenSnippets<'a> { + cx: Cow<'a, str>, + lint: Cow<'a, str>, + span: Cow<'a, str>, + msg: Cow<'a, str>, +} + +fn get_and_then_snippets<'a, 'hir>( + cx: &LateContext<'_, '_>, + and_then_snippets: &'hir [Expr<'hir>], +) -> AndThenSnippets<'a> { + let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx"); + let lint_snippet = snippet(cx, and_then_snippets[1].span, ".."); + let span_snippet = snippet(cx, and_then_snippets[2].span, "span"); + let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#); + + AndThenSnippets { + cx: cx_snippet, + lint: lint_snippet, + span: span_snippet, + msg: msg_snippet, + } +} + +struct SpanSuggestionSnippets<'a> { + help: Cow<'a, str>, + sugg: Cow<'a, str>, + applicability: Cow<'a, str>, +} + +fn span_suggestion_snippets<'a, 'hir>( + cx: &LateContext<'_, '_>, + span_call_args: &'hir [Expr<'hir>], +) -> SpanSuggestionSnippets<'a> { + let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#); + let sugg_snippet = snippet(cx, span_call_args[3].span, ".."); + let applicability_snippet = snippet(cx, span_call_args[4].span, "Applicability::MachineApplicable"); + + SpanSuggestionSnippets { + help: help_snippet, + sugg: sugg_snippet, + applicability: applicability_snippet, + } +} + +fn suggest_suggestion( + cx: &LateContext<'_, '_>, + expr: &Expr<'_>, + and_then_snippets: &AndThenSnippets<'_>, + span_suggestion_snippets: &SpanSuggestionSnippets<'_>, +) { + span_lint_and_sugg( + cx, + COLLAPSIBLE_SPAN_LINT_CALLS, + expr.span, + "this call is collapsible", + "collapse into", + format!( + "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})", + and_then_snippets.cx, + and_then_snippets.lint, + and_then_snippets.span, + and_then_snippets.msg, + span_suggestion_snippets.help, + span_suggestion_snippets.sugg, + span_suggestion_snippets.applicability + ), + Applicability::MachineApplicable, + ); +} + +fn suggest_help( + cx: &LateContext<'_, '_>, + expr: &Expr<'_>, + and_then_snippets: &AndThenSnippets<'_>, + help: &str, + with_span: bool, +) { + let option_span = if with_span { + format!("Some({})", and_then_snippets.span) + } else { + "None".to_string() + }; + + span_lint_and_sugg( + cx, + COLLAPSIBLE_SPAN_LINT_CALLS, + expr.span, + "this call is collapsible", + "collapse into", + format!( + "span_lint_and_help({}, {}, {}, {}, {}, {})", + and_then_snippets.cx, + and_then_snippets.lint, + and_then_snippets.span, + and_then_snippets.msg, + &option_span, + help + ), + Applicability::MachineApplicable, + ); +} + +fn suggest_note( + cx: &LateContext<'_, '_>, + expr: &Expr<'_>, + and_then_snippets: &AndThenSnippets<'_>, + note: &str, + with_span: bool, +) { + let note_span = if with_span { + format!("Some({})", and_then_snippets.span) + } else { + "None".to_string() + }; + + span_lint_and_sugg( + cx, + COLLAPSIBLE_SPAN_LINT_CALLS, + expr.span, + "this call is collspible", + "collapse into", + format!( + "span_lint_and_note({}, {}, {}, {}, {}, {})", + and_then_snippets.cx, + and_then_snippets.lint, + and_then_snippets.span, + and_then_snippets.msg, + note_span, + note + ), + Applicability::MachineApplicable, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs new file mode 100644 index 0000000000000..04b4b42376193 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -0,0 +1,1491 @@ +#[macro_use] +pub mod sym; + +pub mod attrs; +pub mod author; +pub mod camel_case; +pub mod comparisons; +pub mod conf; +pub mod constants; +mod diagnostics; +pub mod higher; +mod hir_utils; +pub mod inspector; +pub mod internal_lints; +pub mod numeric_literal; +pub mod paths; +pub mod ptr; +pub mod sugg; +pub mod usage; +pub use self::attrs::*; +pub use self::diagnostics::*; +pub use self::hir_utils::{SpanlessEq, SpanlessHash}; + +use std::borrow::Cow; +use std::mem; + +use if_chain::if_chain; +use rustc_ast::ast::{self, Attribute, LitKind}; +use rustc_attr as attr; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE}; +use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; +use rustc_hir::Node; +use rustc_hir::{ + def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind, + MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind, Unsafety, +}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, Level, Lint, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::traits; +use rustc_middle::ty::{self, layout::IntegerExt, subst::GenericArg, Binder, Ty, TyCtxt, TypeFoldable}; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::source_map::original_sp; +use rustc_span::symbol::{self, kw, Symbol}; +use rustc_span::{BytePos, Pos, Span, DUMMY_SP}; +use rustc_target::abi::Integer; +use rustc_trait_selection::traits::predicate_for_trait_def; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; +use rustc_trait_selection::traits::query::normalize::AtExt; +use smallvec::SmallVec; + +use crate::consts::{constant, Constant}; +use crate::reexport::Name; + +/// Returns `true` if the two spans come from differing expansions (i.e., one is +/// from a macro and one isn't). +#[must_use] +pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool { + rhs.ctxt() != lhs.ctxt() +} + +/// Returns `true` if the given `NodeId` is inside a constant context +/// +/// # Example +/// +/// ```rust,ignore +/// if in_constant(cx, expr.hir_id) { +/// // Do something +/// } +/// ``` +pub fn in_constant(cx: &LateContext<'_, '_>, id: HirId) -> bool { + let parent_id = cx.tcx.hir().get_parent_item(id); + match cx.tcx.hir().get(parent_id) { + Node::Item(&Item { + kind: ItemKind::Const(..), + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + .. + }) + | Node::AnonConst(_) + | Node::Item(&Item { + kind: ItemKind::Static(..), + .. + }) => true, + Node::Item(&Item { + kind: ItemKind::Fn(ref sig, ..), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(ref sig, _), + .. + }) => sig.header.constness == Constness::Const, + _ => false, + } +} + +/// Returns `true` if this `span` was expanded by any macro. +#[must_use] +pub fn in_macro(span: Span) -> bool { + if span.from_expansion() { + if let ExpnKind::Desugaring(..) = span.ctxt().outer_expn_data().kind { + false + } else { + true + } + } else { + false + } +} +// If the snippet is empty, it's an attribute that was inserted during macro +// expansion and we want to ignore those, because they could come from external +// sources that the user has no control over. +// For some reason these attributes don't have any expansion info on them, so +// we have to check it this way until there is a better way. +pub fn is_present_in_source(cx: &T, span: Span) -> bool { + if let Some(snippet) = snippet_opt(cx, span) { + if snippet.is_empty() { + return false; + } + } + true +} + +/// Checks if given pattern is a wildcard (`_`) +pub fn is_wild<'tcx>(pat: &impl std::ops::Deref>) -> bool { + match pat.kind { + PatKind::Wild => true, + _ => false, + } +} + +/// Checks if type is struct, enum or union type with the given def path. +pub fn match_type(cx: &LateContext<'_, '_>, ty: Ty<'_>, path: &[&str]) -> bool { + match ty.kind { + ty::Adt(adt, _) => match_def_path(cx, adt.did, path), + _ => false, + } +} + +/// Checks if the type is equal to a diagnostic item +pub fn is_type_diagnostic_item(cx: &LateContext<'_, '_>, ty: Ty<'_>, diag_item: Symbol) -> bool { + match ty.kind { + ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did), + _ => false, + } +} + +/// Checks if the method call given in `expr` belongs to the given trait. +pub fn match_trait_method(cx: &LateContext<'_, '_>, expr: &Expr<'_>, path: &[&str]) -> bool { + let def_id = cx.tables.type_dependent_def_id(expr.hir_id).unwrap(); + let trt_id = cx.tcx.trait_of_item(def_id); + if let Some(trt_id) = trt_id { + match_def_path(cx, trt_id, path) + } else { + false + } +} + +/// Checks if an expression references a variable of the given name. +pub fn match_var(expr: &Expr<'_>, var: Name) -> bool { + if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind { + if path.segments.len() == 1 && path.segments[0].ident.name == var { + return true; + } + } + false +} + +pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { + match *path { + QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"), + QPath::TypeRelative(_, ref seg) => seg, + } +} + +pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { + match *path { + QPath::Resolved(_, ref path) if path.segments.len() == 1 => Some(&path.segments[0]), + QPath::Resolved(..) => None, + QPath::TypeRelative(_, ref seg) => Some(seg), + } +} + +/// Matches a `QPath` against a slice of segment string literals. +/// +/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a +/// `rustc_hir::QPath`. +/// +/// # Examples +/// ```rust,ignore +/// match_qpath(path, &["std", "rt", "begin_unwind"]) +/// ``` +pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { + match *path { + QPath::Resolved(_, ref path) => match_path(path, segments), + QPath::TypeRelative(ref ty, ref segment) => match ty.kind { + TyKind::Path(ref inner_path) => { + !segments.is_empty() + && match_qpath(inner_path, &segments[..(segments.len() - 1)]) + && segment.ident.name.as_str() == segments[segments.len() - 1] + }, + _ => false, + }, + } +} + +/// Matches a `Path` against a slice of segment string literals. +/// +/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a +/// `rustc_hir::Path`. +/// +/// # Examples +/// +/// ```rust,ignore +/// if match_path(&trait_ref.path, &paths::HASH) { +/// // This is the `std::hash::Hash` trait. +/// } +/// +/// if match_path(ty_path, &["rustc", "lint", "Lint"]) { +/// // This is a `rustc_middle::lint::Lint`. +/// } +/// ``` +pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool { + path.segments + .iter() + .rev() + .zip(segments.iter().rev()) + .all(|(a, b)| a.ident.name.as_str() == *b) +} + +/// Matches a `Path` against a slice of segment string literals, e.g. +/// +/// # Examples +/// ```rust,ignore +/// match_path_ast(path, &["std", "rt", "begin_unwind"]) +/// ``` +pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool { + path.segments + .iter() + .rev() + .zip(segments.iter().rev()) + .all(|(a, b)| a.ident.name.as_str() == *b) +} + +/// Gets the definition associated to a path. +pub fn path_to_res(cx: &LateContext<'_, '_>, path: &[&str]) -> Option { + let crates = cx.tcx.crates(); + let krate = crates + .iter() + .find(|&&krate| cx.tcx.crate_name(krate).as_str() == path[0]); + if let Some(krate) = krate { + let krate = DefId { + krate: *krate, + index: CRATE_DEF_INDEX, + }; + let mut items = cx.tcx.item_children(krate); + let mut path_it = path.iter().skip(1).peekable(); + + loop { + let segment = match path_it.next() { + Some(segment) => segment, + None => return None, + }; + + let result = SmallVec::<[_; 8]>::new(); + for item in mem::replace(&mut items, cx.tcx.arena.alloc_slice(&result)).iter() { + if item.ident.name.as_str() == *segment { + if path_it.peek().is_none() { + return Some(item.res); + } + + items = cx.tcx.item_children(item.res.def_id()); + break; + } + } + } + } else { + None + } +} + +pub fn qpath_res(cx: &LateContext<'_, '_>, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res { + match qpath { + hir::QPath::Resolved(_, path) => path.res, + hir::QPath::TypeRelative(..) => { + if cx.tcx.has_typeck_tables(id.owner.to_def_id()) { + cx.tcx + .typeck_tables_of(id.owner.to_def_id().expect_local()) + .qpath_res(qpath, id) + } else { + Res::Err + } + }, + } +} + +/// Convenience function to get the `DefId` of a trait by path. +/// It could be a trait or trait alias. +pub fn get_trait_def_id(cx: &LateContext<'_, '_>, path: &[&str]) -> Option { + let res = match path_to_res(cx, path) { + Some(res) => res, + None => return None, + }; + + match res { + Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), + Res::Err => unreachable!("this trait resolution is impossible: {:?}", &path), + _ => None, + } +} + +/// Checks whether a type implements a trait. +/// See also `get_trait_def_id`. +pub fn implements_trait<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + ty_params: &[GenericArg<'tcx>], +) -> bool { + let ty = cx.tcx.erase_regions(&ty); + let obligation = predicate_for_trait_def( + cx.tcx, + cx.param_env, + traits::ObligationCause::dummy(), + trait_id, + 0, + ty, + ty_params, + ); + cx.tcx + .infer_ctxt() + .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation)) +} + +/// Gets the `hir::TraitRef` of the trait the given method is implemented for. +/// +/// Use this if you want to find the `TraitRef` of the `Add` trait in this example: +/// +/// ```rust +/// struct Point(isize, isize); +/// +/// impl std::ops::Add for Point { +/// type Output = Self; +/// +/// fn add(self, other: Self) -> Self { +/// Point(0, 0) +/// } +/// } +/// ``` +pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'_, 'tcx>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> { + // Get the implemented trait for the current function + let parent_impl = cx.tcx.hir().get_parent_item(hir_id); + if_chain! { + if parent_impl != hir::CRATE_HIR_ID; + if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl); + if let hir::ItemKind::Impl{ of_trait: trait_ref, .. } = &item.kind; + then { return trait_ref.as_ref(); } + } + None +} + +/// Checks whether this type implements `Drop`. +pub fn has_drop<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { + match ty.ty_adt_def() { + Some(def) => def.has_dtor(cx.tcx), + _ => false, + } +} + +/// Returns the method names and argument list of nested method call expressions that make up +/// `expr`. method/span lists are sorted with the most recent call first. +pub fn method_calls<'tcx>( + expr: &'tcx Expr<'tcx>, + max_depth: usize, +) -> (Vec, Vec<&'tcx [Expr<'tcx>]>, Vec) { + let mut method_names = Vec::with_capacity(max_depth); + let mut arg_lists = Vec::with_capacity(max_depth); + let mut spans = Vec::with_capacity(max_depth); + + let mut current = expr; + for _ in 0..max_depth { + if let ExprKind::MethodCall(path, span, args) = ¤t.kind { + if args.iter().any(|e| e.span.from_expansion()) { + break; + } + method_names.push(path.ident.name); + arg_lists.push(&**args); + spans.push(*span); + current = &args[0]; + } else { + break; + } + } + + (method_names, arg_lists, spans) +} + +/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s. +/// +/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`, +/// `matched_method_chain(expr, &["bar", "baz"])` will return a `Vec` +/// containing the `Expr`s for +/// `.bar()` and `.baz()` +pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option]>> { + let mut current = expr; + let mut matched = Vec::with_capacity(methods.len()); + for method_name in methods.iter().rev() { + // method chains are stored last -> first + if let ExprKind::MethodCall(ref path, _, ref args) = current.kind { + if path.ident.name.as_str() == *method_name { + if args.iter().any(|e| e.span.from_expansion()) { + return None; + } + matched.push(&**args); // build up `matched` backwards + current = &args[0] // go to parent expression + } else { + return None; + } + } else { + return None; + } + } + // Reverse `matched` so that it is in the same order as `methods`. + matched.reverse(); + Some(matched) +} + +/// Returns `true` if the provided `def_id` is an entrypoint to a program. +pub fn is_entrypoint_fn(cx: &LateContext<'_, '_>, def_id: DefId) -> bool { + cx.tcx + .entry_fn(LOCAL_CRATE) + .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_def_id()) +} + +/// Gets the name of the item the expression is in, if available. +pub fn get_item_name(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + match cx.tcx.hir().find(parent_id) { + Some( + Node::Item(Item { ident, .. }) + | Node::TraitItem(TraitItem { ident, .. }) + | Node::ImplItem(ImplItem { ident, .. }), + ) => Some(ident.name), + _ => None, + } +} + +/// Gets the name of a `Pat`, if any. +pub fn get_pat_name(pat: &Pat<'_>) -> Option { + match pat.kind { + PatKind::Binding(.., ref spname, _) => Some(spname.name), + PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name), + PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p), + _ => None, + } +} + +struct ContainsName { + name: Name, + result: bool, +} + +impl<'tcx> Visitor<'tcx> for ContainsName { + type Map = Map<'tcx>; + + fn visit_name(&mut self, _: Span, name: Name) { + if self.name == name { + self.result = true; + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Checks if an `Expr` contains a certain name. +pub fn contains_name(name: Name, expr: &Expr<'_>) -> bool { + let mut cn = ContainsName { name, result: false }; + cn.visit_expr(expr); + cn.result +} + +/// Converts a span to a code snippet if available, otherwise use default. +/// +/// This is useful if you want to provide suggestions for your lint or more generally, if you want +/// to convert a given `Span` to a `str`. +/// +/// # Example +/// ```rust,ignore +/// snippet(cx, expr.span, "..") +/// ``` +pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { + snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from) +} + +/// Same as `snippet`, but it adapts the applicability level by following rules: +/// +/// - Applicability level `Unspecified` will never be changed. +/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. +/// - If the default value is used and the applicability level is `MachineApplicable`, change it to +/// `HasPlaceholders` +pub fn snippet_with_applicability<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + applicability: &mut Applicability, +) -> Cow<'a, str> { + if *applicability != Applicability::Unspecified && span.from_expansion() { + *applicability = Applicability::MaybeIncorrect; + } + snippet_opt(cx, span).map_or_else( + || { + if *applicability == Applicability::MachineApplicable { + *applicability = Applicability::HasPlaceholders; + } + Cow::Borrowed(default) + }, + From::from, + ) +} + +/// Same as `snippet`, but should only be used when it's clear that the input span is +/// not a macro argument. +pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { + snippet(cx, span.source_callsite(), default) +} + +/// Converts a span to a code snippet. Returns `None` if not available. +pub fn snippet_opt(cx: &T, span: Span) -> Option { + cx.sess().source_map().span_to_snippet(span).ok() +} + +/// Converts a span (from a block) to a code snippet if available, otherwise use default. +/// +/// This trims the code of indentation, except for the first line. Use it for blocks or block-like +/// things which need to be printed as such. +/// +/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the +/// resulting snippet of the given span. +/// +/// # Example +/// +/// ```rust,ignore +/// snippet_block(cx, block.span, "..", None) +/// // where, `block` is the block of the if expr +/// if x { +/// y; +/// } +/// // will return the snippet +/// { +/// y; +/// } +/// ``` +/// +/// ```rust,ignore +/// snippet_block(cx, block.span, "..", Some(if_expr.span)) +/// // where, `block` is the block of the if expr +/// if x { +/// y; +/// } +/// // will return the snippet +/// { +/// y; +/// } // aligned with `if` +/// ``` +/// Note that the first line of the snippet always has 0 indentation. +pub fn snippet_block<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + indent_relative_to: Option, +) -> Cow<'a, str> { + let snip = snippet(cx, span, default); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + trim_multiline(snip, true, indent) +} + +/// Same as `snippet_block`, but adapts the applicability level by the rules of +/// `snippet_with_applicabiliy`. +pub fn snippet_block_with_applicability<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + indent_relative_to: Option, + applicability: &mut Applicability, +) -> Cow<'a, str> { + let snip = snippet_with_applicability(cx, span, default, applicability); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + trim_multiline(snip, true, indent) +} + +/// Returns a new Span that extends the original Span to the first non-whitespace char of the first +/// line. +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ +/// // will be converted to +/// let x = (); +/// // ^^^^^^^^^^ +/// ``` +pub fn first_line_of_span(cx: &T, span: Span) -> Span { + if let Some(first_char_pos) = first_char_in_first_line(cx, span) { + span.with_lo(first_char_pos) + } else { + span + } +} + +fn first_char_in_first_line(cx: &T, span: Span) -> Option { + let line_span = line_span(cx, span); + if let Some(snip) = snippet_opt(cx, line_span) { + snip.find(|c: char| !c.is_whitespace()) + .map(|pos| line_span.lo() + BytePos::from_usize(pos)) + } else { + None + } +} + +/// Returns the indentation of the line of a span +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ -- will return 0 +/// let x = (); +/// // ^^ -- will return 4 +/// ``` +pub fn indent_of(cx: &T, span: Span) -> Option { + if let Some(snip) = snippet_opt(cx, line_span(cx, span)) { + snip.find(|c: char| !c.is_whitespace()) + } else { + None + } +} + +/// Extends the span to the beginning of the spans line, incl. whitespaces. +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ +/// // will be converted to +/// let x = (); +/// // ^^^^^^^^^^^^^^ +/// ``` +fn line_span(cx: &T, span: Span) -> Span { + let span = original_sp(span, DUMMY_SP); + let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap(); + let line_no = source_map_and_line.line; + let line_start = source_map_and_line.sf.lines[line_no]; + Span::new(line_start, span.hi(), span.ctxt()) +} + +/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. +/// Also takes an `Option` which can be put inside the braces. +pub fn expr_block<'a, T: LintContext>( + cx: &T, + expr: &Expr<'_>, + option: Option, + default: &'a str, + indent_relative_to: Option, +) -> Cow<'a, str> { + let code = snippet_block(cx, expr.span, default, indent_relative_to); + let string = option.unwrap_or_default(); + if expr.span.from_expansion() { + Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default))) + } else if let ExprKind::Block(_, _) = expr.kind { + Cow::Owned(format!("{}{}", code, string)) + } else if string.is_empty() { + Cow::Owned(format!("{{ {} }}", code)) + } else { + Cow::Owned(format!("{{\n{};\n{}\n}}", code, string)) + } +} + +/// Trim indentation from a multiline string with possibility of ignoring the +/// first line. +fn trim_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option) -> Cow<'_, str> { + let s_space = trim_multiline_inner(s, ignore_first, indent, ' '); + let s_tab = trim_multiline_inner(s_space, ignore_first, indent, '\t'); + trim_multiline_inner(s_tab, ignore_first, indent, ' ') +} + +fn trim_multiline_inner(s: Cow<'_, str>, ignore_first: bool, indent: Option, ch: char) -> Cow<'_, str> { + let mut x = s + .lines() + .skip(ignore_first as usize) + .filter_map(|l| { + if l.is_empty() { + None + } else { + // ignore empty lines + Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0) + } + }) + .min() + .unwrap_or(0); + if let Some(indent) = indent { + x = x.saturating_sub(indent); + } + if x > 0 { + Cow::Owned( + s.lines() + .enumerate() + .map(|(i, l)| { + if (ignore_first && i == 0) || l.is_empty() { + l + } else { + l.split_at(x).1 + } + }) + .collect::>() + .join("\n"), + ) + } else { + s + } +} + +/// Gets the parent expression, if any –- this is useful to constrain a lint. +pub fn get_parent_expr<'c>(cx: &'c LateContext<'_, '_>, e: &Expr<'_>) -> Option<&'c Expr<'c>> { + let map = &cx.tcx.hir(); + let hir_id = e.hir_id; + let parent_id = map.get_parent_node(hir_id); + if hir_id == parent_id { + return None; + } + map.find(parent_id).and_then(|node| { + if let Node::Expr(parent) = node { + Some(parent) + } else { + None + } + }) +} + +pub fn get_enclosing_block<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> { + let map = &cx.tcx.hir(); + let enclosing_node = map + .get_enclosing_scope(hir_id) + .and_then(|enclosing_id| map.find(enclosing_id)); + if let Some(node) = enclosing_node { + match node { + Node::Block(block) => Some(block), + Node::Item(&Item { + kind: ItemKind::Fn(_, _, eid), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(_, eid), + .. + }) => match cx.tcx.hir().body(eid).value.kind { + ExprKind::Block(ref block, _) => Some(block), + _ => None, + }, + _ => None, + } + } else { + None + } +} + +/// Returns the base type for HIR references and pointers. +pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { + match ty.kind { + TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty), + _ => ty, + } +} + +/// Returns the base type for references and raw pointers. +pub fn walk_ptrs_ty(ty: Ty<'_>) -> Ty<'_> { + match ty.kind { + ty::Ref(_, ty, _) => walk_ptrs_ty(ty), + _ => ty, + } +} + +/// Returns the base type for references and raw pointers, and count reference +/// depth. +pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { + fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { + match ty.kind { + ty::Ref(_, ty, _) => inner(ty, depth + 1), + _ => (ty, depth), + } + } + inner(ty, 0) +} + +/// Checks whether the given expression is a constant integer of the given value. +/// unlike `is_integer_literal`, this version does const folding +pub fn is_integer_const(cx: &LateContext<'_, '_>, e: &Expr<'_>, value: u128) -> bool { + if is_integer_literal(e, value) { + return true; + } + let map = cx.tcx.hir(); + let parent_item = map.get_parent_item(e.hir_id); + if let Some((Constant::Int(v), _)) = map + .maybe_body_owned_by(parent_item) + .and_then(|body_id| constant(cx, cx.tcx.body_tables(body_id), e)) + { + value == v + } else { + false + } +} + +/// Checks whether the given expression is a constant literal of the given value. +pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool { + // FIXME: use constant folding + if let ExprKind::Lit(ref spanned) = expr.kind { + if let LitKind::Int(v, _) = spanned.node { + return v == value; + } + } + false +} + +/// Returns `true` if the given `Expr` has been coerced before. +/// +/// Examples of coercions can be found in the Nomicon at +/// . +/// +/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::check::coercion` for more +/// information on adjustments and coercions. +pub fn is_adjusted(cx: &LateContext<'_, '_>, e: &Expr<'_>) -> bool { + cx.tables.adjustments().get(e.hir_id).is_some() +} + +/// Returns the pre-expansion span if is this comes from an expansion of the +/// macro `name`. +/// See also `is_direct_expn_of`. +#[must_use] +pub fn is_expn_of(mut span: Span, name: &str) -> Option { + loop { + if span.from_expansion() { + let data = span.ctxt().outer_expn_data(); + let new_span = data.call_site; + + if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if mac_name.as_str() == name { + return Some(new_span); + } + } + + span = new_span; + } else { + return None; + } + } +} + +/// Returns the pre-expansion span if the span directly comes from an expansion +/// of the macro `name`. +/// The difference with `is_expn_of` is that in +/// ```rust,ignore +/// foo!(bar!(42)); +/// ``` +/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only +/// `bar!` by +/// `is_direct_expn_of`. +#[must_use] +pub fn is_direct_expn_of(span: Span, name: &str) -> Option { + if span.from_expansion() { + let data = span.ctxt().outer_expn_data(); + let new_span = data.call_site; + + if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if mac_name.as_str() == name { + return Some(new_span); + } + } + } + + None +} + +/// Convenience function to get the return type of a function. +pub fn return_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, fn_item: hir::HirId) -> Ty<'tcx> { + let fn_def_id = cx.tcx.hir().local_def_id(fn_item); + let ret_ty = cx.tcx.fn_sig(fn_def_id).output(); + cx.tcx.erase_late_bound_regions(&ret_ty) +} + +/// Checks if two types are the same. +/// +/// This discards any lifetime annotations, too. +// +// FIXME: this works correctly for lifetimes bounds (`for <'a> Foo<'a>` == +// `for <'b> Foo<'b>`, but not for type parameters). +pub fn same_tys<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { + let a = cx.tcx.erase_late_bound_regions(&Binder::bind(a)); + let b = cx.tcx.erase_late_bound_regions(&Binder::bind(b)); + cx.tcx + .infer_ctxt() + .enter(|infcx| infcx.can_eq(cx.param_env, a, b).is_ok()) +} + +/// Returns `true` if the given type is an `unsafe` function. +pub fn type_is_unsafe_function<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind { + ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe, + _ => false, + } +} + +pub fn is_copy<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { + ty.is_copy_modulo_regions(cx.tcx, cx.param_env, DUMMY_SP) +} + +/// Checks if an expression is constructing a tuple-like enum variant or struct +pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(ref fun, _) = expr.kind { + if let ExprKind::Path(ref qp) = fun.kind { + let res = cx.tables.qpath_res(qp, fun.hir_id); + return match res { + def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true, + def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id), + _ => false, + }; + } + } + false +} + +/// Returns `true` if a pattern is refutable. +pub fn is_refutable(cx: &LateContext<'_, '_>, pat: &Pat<'_>) -> bool { + fn is_enum_variant(cx: &LateContext<'_, '_>, qpath: &QPath<'_>, id: HirId) -> bool { + matches!( + cx.tables.qpath_res(qpath, id), + def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _) + ) + } + + fn are_refutable<'a, I: Iterator>>(cx: &LateContext<'_, '_>, mut i: I) -> bool { + i.any(|pat| is_refutable(cx, pat)) + } + + match pat.kind { + PatKind::Binding(..) | PatKind::Wild => false, + PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat), + PatKind::Lit(..) | PatKind::Range(..) => true, + PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), + PatKind::Or(ref pats) | PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)), + PatKind::Struct(ref qpath, ref fields, _) => { + if is_enum_variant(cx, qpath, pat.hir_id) { + true + } else { + are_refutable(cx, fields.iter().map(|field| &*field.pat)) + } + }, + PatKind::TupleStruct(ref qpath, ref pats, _) => { + if is_enum_variant(cx, qpath, pat.hir_id) { + true + } else { + are_refutable(cx, pats.iter().map(|pat| &**pat)) + } + }, + PatKind::Slice(ref head, ref middle, ref tail) => { + are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)) + }, + } +} + +/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d +/// implementations have. +pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { + attr::contains_name(attrs, sym!(automatically_derived)) +} + +/// Remove blocks around an expression. +/// +/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return +/// themselves. +pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + while let ExprKind::Block(ref block, ..) = expr.kind { + match (block.stmts.is_empty(), block.expr.as_ref()) { + (true, Some(e)) => expr = e, + _ => break, + } + } + expr +} + +pub fn is_self(slf: &Param<'_>) -> bool { + if let PatKind::Binding(.., name, _) = slf.pat.kind { + name.name == kw::SelfLower + } else { + false + } +} + +pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { + if_chain! { + if let TyKind::Path(ref qp) = slf.kind; + if let QPath::Resolved(None, ref path) = *qp; + if let Res::SelfTy(..) = path.res; + then { + return true + } + } + false +} + +pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator> { + (0..decl.inputs.len()).map(move |i| &body.params[i]) +} + +/// Checks if a given expression is a match expression expanded from the `?` +/// operator or the `try` macro. +pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + fn is_ok(arm: &Arm<'_>) -> bool { + if_chain! { + if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind; + if match_qpath(path, &paths::RESULT_OK[1..]); + if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind; + if let ExprKind::Path(QPath::Resolved(None, ref path)) = arm.body.kind; + if let Res::Local(lid) = path.res; + if lid == hir_id; + then { + return true; + } + } + false + } + + fn is_err(arm: &Arm<'_>) -> bool { + if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind { + match_qpath(path, &paths::RESULT_ERR[1..]) + } else { + false + } + } + + if let ExprKind::Match(_, ref arms, ref source) = expr.kind { + // desugared from a `?` operator + if let MatchSource::TryDesugar = *source { + return Some(expr); + } + + if_chain! { + if arms.len() == 2; + if arms[0].guard.is_none(); + if arms[1].guard.is_none(); + if (is_ok(&arms[0]) && is_err(&arms[1])) || + (is_ok(&arms[1]) && is_err(&arms[0])); + then { + return Some(expr); + } + } + } + + None +} + +/// Returns `true` if the lint is allowed in the current context +/// +/// Useful for skipping long running code when it's unnecessary +pub fn is_allowed(cx: &LateContext<'_, '_>, lint: &'static Lint, id: HirId) -> bool { + cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow +} + +pub fn get_arg_name(pat: &Pat<'_>) -> Option { + match pat.kind { + PatKind::Binding(.., ident, None) => Some(ident.name), + PatKind::Ref(ref subpat, _) => get_arg_name(subpat), + _ => None, + } +} + +pub fn int_bits(tcx: TyCtxt<'_>, ity: ast::IntTy) -> u64 { + Integer::from_attr(&tcx, attr::IntType::SignedInt(ity)).size().bits() +} + +#[allow(clippy::cast_possible_wrap)] +/// Turn a constant int byte representation into an i128 +pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: ast::IntTy) -> i128 { + let amt = 128 - int_bits(tcx, ity); + ((u as i128) << amt) >> amt +} + +#[allow(clippy::cast_sign_loss)] +/// clip unused bytes +pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: ast::IntTy) -> u128 { + let amt = 128 - int_bits(tcx, ity); + ((u as u128) << amt) >> amt +} + +/// clip unused bytes +pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: ast::UintTy) -> u128 { + let bits = Integer::from_attr(&tcx, attr::IntType::UnsignedInt(ity)).size().bits(); + let amt = 128 - bits; + (u << amt) >> amt +} + +/// Removes block comments from the given `Vec` of lines. +/// +/// # Examples +/// +/// ```rust,ignore +/// without_block_comments(vec!["/*", "foo", "*/"]); +/// // => vec![] +/// +/// without_block_comments(vec!["bar", "/*", "foo", "*/"]); +/// // => vec!["bar"] +/// ``` +pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> { + let mut without = vec![]; + + let mut nest_level = 0; + + for line in lines { + if line.contains("/*") { + nest_level += 1; + continue; + } else if line.contains("*/") { + nest_level -= 1; + continue; + } + + if nest_level == 0 { + without.push(line); + } + } + + without +} + +pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { + let map = &tcx.hir(); + let mut prev_enclosing_node = None; + let mut enclosing_node = node; + while Some(enclosing_node) != prev_enclosing_node { + if is_automatically_derived(map.attrs(enclosing_node)) { + return true; + } + prev_enclosing_node = Some(enclosing_node); + enclosing_node = map.get_parent_item(enclosing_node); + } + false +} + +/// Returns true if ty has `iter` or `iter_mut` methods +pub fn has_iter_method(cx: &LateContext<'_, '_>, probably_ref_ty: Ty<'_>) -> Option<&'static str> { + // FIXME: instead of this hard-coded list, we should check if `::iter` + // exists and has the desired signature. Unfortunately FnCtxt is not exported + // so we can't use its `lookup_method` method. + let into_iter_collections: [&[&str]; 13] = [ + &paths::VEC, + &paths::OPTION, + &paths::RESULT, + &paths::BTREESET, + &paths::BTREEMAP, + &paths::VEC_DEQUE, + &paths::LINKED_LIST, + &paths::BINARY_HEAP, + &paths::HASHSET, + &paths::HASHMAP, + &paths::PATH_BUF, + &paths::PATH, + &paths::RECEIVER, + ]; + + let ty_to_check = match probably_ref_ty.kind { + ty::Ref(_, ty_to_check, _) => ty_to_check, + _ => probably_ref_ty, + }; + + let def_id = match ty_to_check.kind { + ty::Array(..) => return Some("array"), + ty::Slice(..) => return Some("slice"), + ty::Adt(adt, _) => adt.did, + _ => return None, + }; + + for path in &into_iter_collections { + if match_def_path(cx, def_id, path) { + return Some(*path.last().unwrap()); + } + } + None +} + +/// Matches a function call with the given path and returns the arguments. +/// +/// Usage: +/// +/// ```rust,ignore +/// if let Some(args) = match_function_call(cx, begin_panic_call, &paths::BEGIN_PANIC); +/// ``` +pub fn match_function_call<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + expr: &'tcx Expr<'_>, + path: &[&str], +) -> Option<&'tcx [Expr<'tcx>]> { + if_chain! { + if let ExprKind::Call(ref fun, ref args) = expr.kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if let Some(fun_def_id) = cx.tables.qpath_res(qpath, fun.hir_id).opt_def_id(); + if match_def_path(cx, fun_def_id, path); + then { + return Some(&args) + } + }; + None +} + +/// Checks if `Ty` is normalizable. This function is useful +/// to avoid crashes on `layout_of`. +pub fn is_normalizable<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool { + cx.tcx.infer_ctxt().enter(|infcx| { + let cause = rustc_middle::traits::ObligationCause::dummy(); + infcx.at(&cause, param_env).normalize(&ty).is_ok() + }) +} + +pub fn match_def_path<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, did: DefId, syms: &[&str]) -> bool { + // We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path` + // accepts only that. We should probably move to Symbols in Clippy as well. + let syms = syms.iter().map(|p| Symbol::intern(p)).collect::>(); + cx.match_def_path(did, &syms) +} + +/// Returns the list of condition expressions and the list of blocks in a +/// sequence of `if/else`. +/// E.g., this returns `([a, b], [c, d, e])` for the expression +/// `if a { c } else if b { d } else { e }`. +pub fn if_sequence<'tcx>( + mut expr: &'tcx Expr<'tcx>, +) -> (SmallVec<[&'tcx Expr<'tcx>; 1]>, SmallVec<[&'tcx Block<'tcx>; 1]>) { + let mut conds = SmallVec::new(); + let mut blocks: SmallVec<[&Block<'_>; 1]> = SmallVec::new(); + + while let Some((ref cond, ref then_expr, ref else_expr)) = higher::if_block(&expr) { + conds.push(&**cond); + if let ExprKind::Block(ref block, _) = then_expr.kind { + blocks.push(block); + } else { + panic!("ExprKind::If node is not an ExprKind::Block"); + } + + if let Some(ref else_expr) = *else_expr { + expr = else_expr; + } else { + break; + } + } + + // final `else {..}` + if !blocks.is_empty() { + if let ExprKind::Block(ref block, _) = expr.kind { + blocks.push(&**block); + } + } + + (conds, blocks) +} + +pub fn parent_node_is_if_expr<'a, 'b>(expr: &Expr<'_>, cx: &LateContext<'a, 'b>) -> bool { + let map = cx.tcx.hir(); + let parent_id = map.get_parent_node(expr.hir_id); + let parent_node = map.get(parent_id); + + match parent_node { + Node::Expr(e) => higher::if_block(&e).is_some(), + Node::Arm(e) => higher::if_block(&e.body).is_some(), + _ => false, + } +} + +// Finds the attribute with the given name, if any +pub fn attr_by_name<'a>(attrs: &'a [Attribute], name: &'_ str) -> Option<&'a Attribute> { + attrs + .iter() + .find(|attr| attr.ident().map_or(false, |ident| ident.as_str() == name)) +} + +// Finds the `#[must_use]` attribute, if any +pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { + attr_by_name(attrs, "must_use") +} + +// Returns whether the type has #[must_use] attribute +pub fn is_must_use_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind { + ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(), + ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(), + ty::Slice(ref ty) + | ty::Array(ref ty, _) + | ty::RawPtr(ty::TypeAndMut { ref ty, .. }) + | ty::Ref(_, ref ty, _) => { + // for the Array case we don't need to care for the len == 0 case + // because we don't want to lint functions returning empty arrays + is_must_use_ty(cx, *ty) + }, + ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)), + ty::Opaque(ref def_id, _) => { + for (predicate, _) in cx.tcx.predicates_of(*def_id).predicates { + if let ty::Predicate::Trait(ref poly_trait_predicate, _) = predicate { + if must_use_attr(&cx.tcx.get_attrs(poly_trait_predicate.skip_binder().trait_ref.def_id)).is_some() { + return true; + } + } + } + false + }, + ty::Dynamic(binder, _) => { + for predicate in binder.skip_binder().iter() { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate { + if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() { + return true; + } + } + } + false + }, + _ => false, + } +} + +// check if expr is calling method or function with #[must_use] attribyte +pub fn is_must_use_func_call(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + let did = match expr.kind { + ExprKind::Call(ref path, _) => if_chain! { + if let ExprKind::Path(ref qpath) = path.kind; + if let def::Res::Def(_, did) = cx.tables.qpath_res(qpath, path.hir_id); + then { + Some(did) + } else { + None + } + }, + ExprKind::MethodCall(_, _, _) => cx.tables.type_dependent_def_id(expr.hir_id), + _ => None, + }; + + if let Some(did) = did { + must_use_attr(&cx.tcx.get_attrs(did)).is_some() + } else { + false + } +} + +pub fn is_no_std_crate(krate: &Crate<'_>) -> bool { + krate.item.attrs.iter().any(|attr| { + if let ast::AttrKind::Normal(ref attr) = attr.kind { + attr.path == symbol::sym::no_std + } else { + false + } + }) +} + +/// Check if parent of a hir node is a trait implementation block. +/// For example, `f` in +/// ```rust,ignore +/// impl Trait for S { +/// fn f() {} +/// } +/// ``` +pub fn is_trait_impl_item(cx: &LateContext<'_, '_>, hir_id: HirId) -> bool { + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. }) + } else { + false + } +} + +/// Check if it's even possible to satisfy the `where` clause for the item. +/// +/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example: +/// +/// ```ignore +/// fn foo() where i32: Iterator { +/// for _ in 2i32 {} +/// } +/// ``` +pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_, '_>, did: DefId) -> bool { + use rustc_trait_selection::traits; + let predicates = + cx.tcx + .predicates_of(did) + .predicates + .iter() + .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); + !traits::normalize_and_test_predicates( + cx.tcx, + traits::elaborate_predicates(cx.tcx, predicates) + .map(|o| o.predicate) + .collect::>(), + ) +} + +pub fn run_lints(cx: &LateContext<'_, '_>, lints: &[&'static Lint], id: HirId) -> bool { + lints.iter().any(|lint| { + matches!( + cx.tcx.lint_level_at_node(lint, id), + (Level::Forbid | Level::Deny | Level::Warn, _) + ) + }) +} + +#[cfg(test)] +mod test { + use super::{trim_multiline, without_block_comments}; + + #[test] + fn test_trim_multiline_single_line() { + assert_eq!("", trim_multiline("".into(), false, None)); + assert_eq!("...", trim_multiline("...".into(), false, None)); + assert_eq!("...", trim_multiline(" ...".into(), false, None)); + assert_eq!("...", trim_multiline("\t...".into(), false, None)); + assert_eq!("...", trim_multiline("\t\t...".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_trim_multiline_block() { + assert_eq!("\ + if x { + y + } else { + z + }", trim_multiline(" if x { + y + } else { + z + }".into(), false, None)); + assert_eq!("\ + if x { + \ty + } else { + \tz + }", trim_multiline(" if x { + \ty + } else { + \tz + }".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_trim_multiline_empty_line() { + assert_eq!("\ + if x { + y + + } else { + z + }", trim_multiline(" if x { + y + + } else { + z + }".into(), false, None)); + } + + #[test] + fn test_without_block_comments_lines_without_block_comments() { + let result = without_block_comments(vec!["/*", "", "*/"]); + println!("result: {:?}", result); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]); + assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]); + + let result = without_block_comments(vec!["/* rust", "", "*/"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* one-line comment */"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["foo", "bar", "baz"]); + assert_eq!(result, vec!["foo", "bar", "baz"]); + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs b/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs new file mode 100644 index 0000000000000..99413153d49bb --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs @@ -0,0 +1,227 @@ +use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind}; + +#[derive(Debug, PartialEq)] +pub enum Radix { + Binary, + Octal, + Decimal, + Hexadecimal, +} + +impl Radix { + /// Returns a reasonable digit group size for this radix. + #[must_use] + fn suggest_grouping(&self) -> usize { + match *self { + Self::Binary | Self::Hexadecimal => 4, + Self::Octal | Self::Decimal => 3, + } + } +} + +/// A helper method to format numeric literals with digit grouping. +/// `lit` must be a valid numeric literal without suffix. +pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String { + NumericLiteral::new(lit, type_suffix, float).format() +} + +#[derive(Debug)] +pub struct NumericLiteral<'a> { + /// Which radix the literal was represented in. + pub radix: Radix, + /// The radix prefix, if present. + pub prefix: Option<&'a str>, + + /// The integer part of the number. + pub integer: &'a str, + /// The fraction part of the number. + pub fraction: Option<&'a str>, + /// The character used as exponent seperator (b'e' or b'E') and the exponent part. + pub exponent: Option<(char, &'a str)>, + + /// The type suffix, including preceding underscore if present. + pub suffix: Option<&'a str>, +} + +impl<'a> NumericLiteral<'a> { + pub fn from_lit(src: &'a str, lit: &Lit) -> Option> { + NumericLiteral::from_lit_kind(src, &lit.kind) + } + + pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option> { + if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) { + let (unsuffixed, suffix) = split_suffix(&src, lit_kind); + let float = if let LitKind::Float(..) = lit_kind { true } else { false }; + Some(NumericLiteral::new(unsuffixed, suffix, float)) + } else { + None + } + } + + #[must_use] + pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self { + // Determine delimiter for radix prefix, if present, and radix. + let radix = if lit.starts_with("0x") { + Radix::Hexadecimal + } else if lit.starts_with("0b") { + Radix::Binary + } else if lit.starts_with("0o") { + Radix::Octal + } else { + Radix::Decimal + }; + + // Grab part of the literal after prefix, if present. + let (prefix, mut sans_prefix) = if let Radix::Decimal = radix { + (None, lit) + } else { + let (p, s) = lit.split_at(2); + (Some(p), s) + }; + + if suffix.is_some() && sans_prefix.ends_with('_') { + // The '_' before the suffix isn't part of the digits + sans_prefix = &sans_prefix[..sans_prefix.len() - 1]; + } + + let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float); + + Self { + radix, + prefix, + integer, + fraction, + exponent, + suffix, + } + } + + pub fn is_decimal(&self) -> bool { + self.radix == Radix::Decimal + } + + pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(char, &str)>) { + let mut integer = digits; + let mut fraction = None; + let mut exponent = None; + + if float { + for (i, c) in digits.char_indices() { + match c { + '.' => { + integer = &digits[..i]; + fraction = Some(&digits[i + 1..]); + }, + 'e' | 'E' => { + if integer.len() > i { + integer = &digits[..i]; + } else { + fraction = Some(&digits[integer.len() + 1..i]); + }; + exponent = Some((c, &digits[i + 1..])); + break; + }, + _ => {}, + } + } + } + + (integer, fraction, exponent) + } + + /// Returns literal formatted in a sensible way. + pub fn format(&self) -> String { + let mut output = String::new(); + + if let Some(prefix) = self.prefix { + output.push_str(prefix); + } + + let group_size = self.radix.suggest_grouping(); + + Self::group_digits( + &mut output, + self.integer, + group_size, + true, + self.radix == Radix::Hexadecimal, + ); + + if let Some(fraction) = self.fraction { + output.push('.'); + Self::group_digits(&mut output, fraction, group_size, false, false); + } + + if let Some((separator, exponent)) = self.exponent { + output.push(separator); + Self::group_digits(&mut output, exponent, group_size, true, false); + } + + if let Some(suffix) = self.suffix { + output.push('_'); + output.push_str(suffix); + } + + output + } + + pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) { + debug_assert!(group_size > 0); + + let mut digits = input.chars().filter(|&c| c != '_'); + + let first_group_size; + + if partial_group_first { + first_group_size = (digits.clone().count() - 1) % group_size + 1; + if pad { + for _ in 0..group_size - first_group_size { + output.push('0'); + } + } + } else { + first_group_size = group_size; + } + + for _ in 0..first_group_size { + if let Some(digit) = digits.next() { + output.push(digit); + } + } + + for (c, i) in digits.zip((0..group_size).cycle()) { + if i == 0 { + output.push('_'); + } + output.push(c); + } + } +} + +fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) { + debug_assert!(lit_kind.is_numeric()); + if let Some(suffix_length) = lit_suffix_length(lit_kind) { + let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length); + (unsuffixed, Some(suffix)) + } else { + (src, None) + } +} + +fn lit_suffix_length(lit_kind: &LitKind) -> Option { + debug_assert!(lit_kind.is_numeric()); + let suffix = match lit_kind { + LitKind::Int(_, int_lit_kind) => match int_lit_kind { + LitIntType::Signed(int_ty) => Some(int_ty.name_str()), + LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()), + LitIntType::Unsuffixed => None, + }, + LitKind::Float(_, float_lit_kind) => match float_lit_kind { + LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()), + LitFloatType::Unsuffixed => None, + }, + _ => None, + }; + + suffix.map(str::len) +} diff --git a/src/tools/clippy/clippy_lints/src/utils/paths.rs b/src/tools/clippy/clippy_lints/src/utils/paths.rs new file mode 100644 index 0000000000000..ca2c4ade15565 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/paths.rs @@ -0,0 +1,139 @@ +//! This module contains paths to types and functions Clippy needs to know +//! about. +//! +//! Whenever possible, please consider diagnostic items over hardcoded paths. +//! See for more information. + +pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"]; +pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"]; +pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"]; +pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"]; +pub const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"]; +pub const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"]; +pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"]; +pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"]; +pub const BOX: [&str; 3] = ["alloc", "boxed", "Box"]; +pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"]; +pub const BTREEMAP_ENTRY: [&str; 5] = ["alloc", "collections", "btree", "map", "Entry"]; +pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"]; +pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"]; +pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; +pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"]; +pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"]; +pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; +pub const CSTRING: [&str; 4] = ["std", "ffi", "c_str", "CString"]; +pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"]; +pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"]; +pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; +pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; +pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; +pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"]; +pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"]; +pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"]; +pub const DROP: [&str; 3] = ["core", "mem", "drop"]; +pub const DROP_TRAIT: [&str; 4] = ["core", "ops", "drop", "Drop"]; +pub const DURATION: [&str; 3] = ["core", "time", "Duration"]; +pub const EARLY_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "EarlyContext"]; +pub const EXIT: [&str; 3] = ["std", "process", "exit"]; +pub const FILE: [&str; 3] = ["std", "fs", "File"]; +pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"]; +pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"]; +pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"]; +pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"]; +pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"]; +pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"]; +pub const HASH: [&str; 2] = ["hash", "Hash"]; +pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"]; +pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; +pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"]; +pub const INDEX: [&str; 3] = ["core", "ops", "Index"]; +pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"]; +pub const INTO: [&str; 3] = ["core", "convert", "Into"]; +pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"]; +pub const IO_READ: [&str; 3] = ["std", "io", "Read"]; +pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"]; +pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"]; +pub const LATE_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "LateContext"]; +pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"]; +pub const LINT: [&str; 3] = ["rustc_session", "lint", "Lint"]; +pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"]; +pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"]; +pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"]; +pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"]; +pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"]; +pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"]; +pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"]; +pub const OPS_MODULE: [&str; 2] = ["core", "ops"]; +pub const OPTION: [&str; 3] = ["core", "option", "Option"]; +pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"]; +pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"]; +pub const ORD: [&str; 3] = ["core", "cmp", "Ord"]; +pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"]; +pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"]; +pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"]; +pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"]; +pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"]; +pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"]; +pub const PATH: [&str; 3] = ["std", "path", "Path"]; +pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"]; +pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"]; +pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"]; +pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"]; +pub const PTR_NULL: [&str; 2] = ["ptr", "null"]; +pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"]; +pub const RANGE: [&str; 3] = ["core", "ops", "Range"]; +pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"]; +pub const RANGE_FROM: [&str; 3] = ["core", "ops", "RangeFrom"]; +pub const RANGE_FROM_STD: [&str; 3] = ["std", "ops", "RangeFrom"]; +pub const RANGE_FULL: [&str; 3] = ["core", "ops", "RangeFull"]; +pub const RANGE_FULL_STD: [&str; 3] = ["std", "ops", "RangeFull"]; +pub const RANGE_INCLUSIVE_NEW: [&str; 4] = ["core", "ops", "RangeInclusive", "new"]; +pub const RANGE_INCLUSIVE_STD_NEW: [&str; 4] = ["std", "ops", "RangeInclusive", "new"]; +pub const RANGE_STD: [&str; 3] = ["std", "ops", "Range"]; +pub const RANGE_TO: [&str; 3] = ["core", "ops", "RangeTo"]; +pub const RANGE_TO_INCLUSIVE: [&str; 3] = ["core", "ops", "RangeToInclusive"]; +pub const RANGE_TO_INCLUSIVE_STD: [&str; 3] = ["std", "ops", "RangeToInclusive"]; +pub const RANGE_TO_STD: [&str; 3] = ["std", "ops", "RangeTo"]; +pub const RC: [&str; 3] = ["alloc", "rc", "Rc"]; +pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"]; +pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"]; +pub const REGEX: [&str; 3] = ["regex", "re_unicode", "Regex"]; +pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"]; +pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"]; +pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"]; +pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"]; +pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"]; +pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"]; +pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"]; +pub const RESULT: [&str; 3] = ["core", "result", "Result"]; +pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"]; +pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"]; +pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"]; +pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"]; +pub const SERDE_DESERIALIZE: [&str; 2] = ["_serde", "Deserialize"]; +pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"]; +pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "", "into_vec"]; +pub const SLICE_ITER: [&str; 3] = ["core", "slice", "Iter"]; +pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"]; +pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"]; +pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"]; +pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"]; +pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"]; +pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"]; +pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"]; +pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]; +pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"]; +pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"]; +pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"]; +pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"]; +pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"]; +pub const TRY_FROM_ERROR: [&str; 4] = ["std", "ops", "Try", "from_error"]; +pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"]; +pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"]; +pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"]; +pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; +pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"]; +pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; +pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; +pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; +pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; diff --git a/src/tools/clippy/clippy_lints/src/utils/ptr.rs b/src/tools/clippy/clippy_lints/src/utils/ptr.rs new file mode 100644 index 0000000000000..240bf2449cb5e --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/ptr.rs @@ -0,0 +1,88 @@ +use crate::utils::{get_pat_name, match_var, snippet}; +use rustc_ast::ast::Name; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Body, BodyId, Expr, ExprKind, Param}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use std::borrow::Cow; + +pub fn get_spans( + cx: &LateContext<'_, '_>, + opt_body_id: Option, + idx: usize, + replacements: &[(&'static str, &'static str)], +) -> Option)>> { + if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) { + get_binding_name(&body.params[idx]).map_or_else( + || Some(vec![]), + |name| extract_clone_suggestions(cx, name, replacements, body), + ) + } else { + Some(vec![]) + } +} + +fn extract_clone_suggestions<'a, 'tcx>( + cx: &LateContext<'a, 'tcx>, + name: Name, + replace: &[(&'static str, &'static str)], + body: &'tcx Body<'_>, +) -> Option)>> { + let mut visitor = PtrCloneVisitor { + cx, + name, + replace, + spans: vec![], + abort: false, + }; + visitor.visit_body(body); + if visitor.abort { + None + } else { + Some(visitor.spans) + } +} + +struct PtrCloneVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + name: Name, + replace: &'a [(&'static str, &'static str)], + spans: Vec<(Span, Cow<'static, str>)>, + abort: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.abort { + return; + } + if let ExprKind::MethodCall(ref seg, _, ref args) = expr.kind { + if args.len() == 1 && match_var(&args[0], self.name) { + if seg.ident.name.as_str() == "capacity" { + self.abort = true; + return; + } + for &(fn_name, suffix) in self.replace { + if seg.ident.name.as_str() == fn_name { + self.spans + .push((expr.span, snippet(self.cx, args[0].span, "_") + suffix)); + return; + } + } + } + return; + } + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +fn get_binding_name(arg: &Param<'_>) -> Option { + get_pat_name(&arg.pat) +} diff --git a/src/tools/clippy/clippy_lints/src/utils/sugg.rs b/src/tools/clippy/clippy_lints/src/utils/sugg.rs new file mode 100644 index 0000000000000..a8fe637d3d978 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/sugg.rs @@ -0,0 +1,628 @@ +//! Contains utility functions to generate suggestions. +#![deny(clippy::missing_docs_in_private_items)] + +use crate::utils::{higher, snippet, snippet_opt, snippet_with_macro_callsite}; +use rustc_ast::util::parser::AssocOp; +use rustc_ast::{ast, token}; +use rustc_ast_pretty::pprust::token_kind_to_string; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{EarlyContext, LateContext, LintContext}; +use rustc_span::source_map::{CharPos, Span}; +use rustc_span::{BytePos, Pos}; +use std::borrow::Cow; +use std::convert::TryInto; +use std::fmt::Display; + +/// A helper type to build suggestion correctly handling parenthesis. +pub enum Sugg<'a> { + /// An expression that never needs parenthesis such as `1337` or `[0; 42]`. + NonParen(Cow<'a, str>), + /// An expression that does not fit in other variants. + MaybeParen(Cow<'a, str>), + /// A binary operator expression, including `as`-casts and explicit type + /// coercion. + BinOp(AssocOp, Cow<'a, str>), +} + +/// Literal constant `1`, for convenience. +pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1")); + +impl Display for Sugg<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match *self { + Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f), + } + } +} + +#[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method +impl<'a> Sugg<'a> { + /// Prepare a suggestion from an expression. + pub fn hir_opt(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) -> Option { + snippet_opt(cx, expr.span).map(|snippet| { + let snippet = Cow::Owned(snippet); + Self::hir_from_snippet(cx, expr, snippet) + }) + } + + /// Convenience function around `hir_opt` for suggestions with a default + /// text. + pub fn hir(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, default: &'a str) -> Self { + Self::hir_opt(cx, expr).unwrap_or_else(|| Sugg::NonParen(Cow::Borrowed(default))) + } + + /// Same as `hir`, but it adapts the applicability level by following rules: + /// + /// - Applicability level `Unspecified` will never be changed. + /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. + /// - If the default value is used and the applicability level is `MachineApplicable`, change it + /// to + /// `HasPlaceholders` + pub fn hir_with_applicability( + cx: &LateContext<'_, '_>, + expr: &hir::Expr<'_>, + default: &'a str, + applicability: &mut Applicability, + ) -> Self { + if *applicability != Applicability::Unspecified && expr.span.from_expansion() { + *applicability = Applicability::MaybeIncorrect; + } + Self::hir_opt(cx, expr).unwrap_or_else(|| { + if *applicability == Applicability::MachineApplicable { + *applicability = Applicability::HasPlaceholders; + } + Sugg::NonParen(Cow::Borrowed(default)) + }) + } + + /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro. + pub fn hir_with_macro_callsite(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, default: &'a str) -> Self { + let snippet = snippet_with_macro_callsite(cx, expr.span, default); + + Self::hir_from_snippet(cx, expr, snippet) + } + + /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*` + /// function variants of `Sugg`, since these use different snippet functions. + fn hir_from_snippet(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self { + if let Some(range) = higher::range(cx, expr) { + let op = match range.limits { + ast::RangeLimits::HalfOpen => AssocOp::DotDot, + ast::RangeLimits::Closed => AssocOp::DotDotEq, + }; + return Sugg::BinOp(op, snippet); + } + + match expr.kind { + hir::ExprKind::AddrOf(..) + | hir::ExprKind::Box(..) + | hir::ExprKind::Closure(..) + | hir::ExprKind::Unary(..) + | hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet), + hir::ExprKind::Continue(..) + | hir::ExprKind::Yield(..) + | hir::ExprKind::Array(..) + | hir::ExprKind::Block(..) + | hir::ExprKind::Break(..) + | hir::ExprKind::Call(..) + | hir::ExprKind::Field(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::LlvmInlineAsm(..) + | hir::ExprKind::Lit(..) + | hir::ExprKind::Loop(..) + | hir::ExprKind::MethodCall(..) + | hir::ExprKind::Path(..) + | hir::ExprKind::Repeat(..) + | hir::ExprKind::Ret(..) + | hir::ExprKind::Struct(..) + | hir::ExprKind::Tup(..) + | hir::ExprKind::DropTemps(_) + | hir::ExprKind::Err => Sugg::NonParen(snippet), + hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet), + hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet), + hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet), + hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet), + hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet), + } + } + + /// Prepare a suggestion from an expression. + pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self { + use rustc_ast::ast::RangeLimits; + + let snippet = snippet(cx, expr.span, default); + + match expr.kind { + ast::ExprKind::AddrOf(..) + | ast::ExprKind::Box(..) + | ast::ExprKind::Closure(..) + | ast::ExprKind::If(..) + | ast::ExprKind::Let(..) + | ast::ExprKind::Unary(..) + | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet), + ast::ExprKind::Async(..) + | ast::ExprKind::Block(..) + | ast::ExprKind::Break(..) + | ast::ExprKind::Call(..) + | ast::ExprKind::Continue(..) + | ast::ExprKind::Yield(..) + | ast::ExprKind::Field(..) + | ast::ExprKind::ForLoop(..) + | ast::ExprKind::Index(..) + | ast::ExprKind::LlvmInlineAsm(..) + | ast::ExprKind::Lit(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::MacCall(..) + | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Paren(..) + | ast::ExprKind::Path(..) + | ast::ExprKind::Repeat(..) + | ast::ExprKind::Ret(..) + | ast::ExprKind::Struct(..) + | ast::ExprKind::Try(..) + | ast::ExprKind::TryBlock(..) + | ast::ExprKind::Tup(..) + | ast::ExprKind::Array(..) + | ast::ExprKind::While(..) + | ast::ExprKind::Await(..) + | ast::ExprKind::Err => Sugg::NonParen(snippet), + ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet), + ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet), + ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet), + ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet), + ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet), + ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet), + ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet), + } + } + + /// Convenience method to create the ` && ` suggestion. + pub fn and(self, rhs: &Self) -> Sugg<'static> { + make_binop(ast::BinOpKind::And, &self, rhs) + } + + /// Convenience method to create the ` & ` suggestion. + pub fn bit_and(self, rhs: &Self) -> Sugg<'static> { + make_binop(ast::BinOpKind::BitAnd, &self, rhs) + } + + /// Convenience method to create the ` as ` suggestion. + pub fn as_ty(self, rhs: R) -> Sugg<'static> { + make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into())) + } + + /// Convenience method to create the `&` suggestion. + pub fn addr(self) -> Sugg<'static> { + make_unop("&", self) + } + + /// Convenience method to create the `&mut ` suggestion. + pub fn mut_addr(self) -> Sugg<'static> { + make_unop("&mut ", self) + } + + /// Convenience method to create the `*` suggestion. + pub fn deref(self) -> Sugg<'static> { + make_unop("*", self) + } + + /// Convenience method to create the `&*` suggestion. Currently this + /// is needed because `sugg.deref().addr()` produces an unnecessary set of + /// parentheses around the deref. + pub fn addr_deref(self) -> Sugg<'static> { + make_unop("&*", self) + } + + /// Convenience method to create the `&mut *` suggestion. Currently + /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary + /// set of parentheses around the deref. + pub fn mut_addr_deref(self) -> Sugg<'static> { + make_unop("&mut *", self) + } + + /// Convenience method to transform suggestion into a return call + pub fn make_return(self) -> Sugg<'static> { + Sugg::NonParen(Cow::Owned(format!("return {}", self))) + } + + /// Convenience method to transform suggestion into a block + /// where the suggestion is a trailing expression + pub fn blockify(self) -> Sugg<'static> { + Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self))) + } + + /// Convenience method to create the `..` or `...` + /// suggestion. + #[allow(dead_code)] + pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> { + match limit { + ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end), + ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end), + } + } + + /// Adds parenthesis to any expression that might need them. Suitable to the + /// `self` argument of a method call + /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`). + pub fn maybe_par(self) -> Self { + match self { + Sugg::NonParen(..) => self, + // `(x)` and `(x).y()` both don't need additional parens. + Sugg::MaybeParen(sugg) => { + if sugg.starts_with('(') && sugg.ends_with(')') { + Sugg::MaybeParen(sugg) + } else { + Sugg::NonParen(format!("({})", sugg).into()) + } + }, + Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()), + } + } +} + +impl<'a, 'b> std::ops::Add> for Sugg<'a> { + type Output = Sugg<'static>; + fn add(self, rhs: Sugg<'b>) -> Sugg<'static> { + make_binop(ast::BinOpKind::Add, &self, &rhs) + } +} + +impl<'a, 'b> std::ops::Sub> for Sugg<'a> { + type Output = Sugg<'static>; + fn sub(self, rhs: Sugg<'b>) -> Sugg<'static> { + make_binop(ast::BinOpKind::Sub, &self, &rhs) + } +} + +impl<'a> std::ops::Not for Sugg<'a> { + type Output = Sugg<'static>; + fn not(self) -> Sugg<'static> { + make_unop("!", self) + } +} + +/// Helper type to display either `foo` or `(foo)`. +struct ParenHelper { + /// `true` if parentheses are needed. + paren: bool, + /// The main thing to display. + wrapped: T, +} + +impl ParenHelper { + /// Builds a `ParenHelper`. + fn new(paren: bool, wrapped: T) -> Self { + Self { paren, wrapped } + } +} + +impl Display for ParenHelper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + if self.paren { + write!(f, "({})", self.wrapped) + } else { + self.wrapped.fmt(f) + } + } +} + +/// Builds the string for `` adding parenthesis when necessary. +/// +/// For convenience, the operator is taken as a string because all unary +/// operators have the same +/// precedence. +pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> { + Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into()) +} + +/// Builds the string for ` ` adding parenthesis when necessary. +/// +/// Precedence of shift operator relative to other arithmetic operation is +/// often confusing so +/// parenthesis will always be added for a mix of these. +pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> { + /// Returns `true` if the operator is a shift operator `<<` or `>>`. + fn is_shift(op: &AssocOp) -> bool { + matches!(*op, AssocOp::ShiftLeft | AssocOp::ShiftRight) + } + + /// Returns `true` if the operator is a arithmetic operator + /// (i.e., `+`, `-`, `*`, `/`, `%`). + fn is_arith(op: &AssocOp) -> bool { + matches!( + *op, + AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus + ) + } + + /// Returns `true` if the operator `op` needs parenthesis with the operator + /// `other` in the direction `dir`. + fn needs_paren(op: &AssocOp, other: &AssocOp, dir: Associativity) -> bool { + other.precedence() < op.precedence() + || (other.precedence() == op.precedence() + && ((op != other && associativity(op) != dir) + || (op == other && associativity(op) != Associativity::Both))) + || is_shift(op) && is_arith(other) + || is_shift(other) && is_arith(op) + } + + let lhs_paren = if let Sugg::BinOp(ref lop, _) = *lhs { + needs_paren(&op, lop, Associativity::Left) + } else { + false + }; + + let rhs_paren = if let Sugg::BinOp(ref rop, _) = *rhs { + needs_paren(&op, rop, Associativity::Right) + } else { + false + }; + + let lhs = ParenHelper::new(lhs_paren, lhs); + let rhs = ParenHelper::new(rhs_paren, rhs); + let sugg = match op { + AssocOp::Add + | AssocOp::BitAnd + | AssocOp::BitOr + | AssocOp::BitXor + | AssocOp::Divide + | AssocOp::Equal + | AssocOp::Greater + | AssocOp::GreaterEqual + | AssocOp::LAnd + | AssocOp::LOr + | AssocOp::Less + | AssocOp::LessEqual + | AssocOp::Modulus + | AssocOp::Multiply + | AssocOp::NotEqual + | AssocOp::ShiftLeft + | AssocOp::ShiftRight + | AssocOp::Subtract => format!( + "{} {} {}", + lhs, + op.to_ast_binop().expect("Those are AST ops").to_string(), + rhs + ), + AssocOp::Assign => format!("{} = {}", lhs, rhs), + AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs), + AssocOp::As => format!("{} as {}", lhs, rhs), + AssocOp::DotDot => format!("{}..{}", lhs, rhs), + AssocOp::DotDotEq => format!("{}..={}", lhs, rhs), + AssocOp::Colon => format!("{}: {}", lhs, rhs), + }; + + Sugg::BinOp(op, sugg.into()) +} + +/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`. +pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> { + make_assoc(AssocOp::from_ast_binop(op), lhs, rhs) +} + +#[derive(PartialEq, Eq, Clone, Copy)] +/// Operator associativity. +enum Associativity { + /// The operator is both left-associative and right-associative. + Both, + /// The operator is left-associative. + Left, + /// The operator is not associative. + None, + /// The operator is right-associative. + Right, +} + +/// Returns the associativity/fixity of an operator. The difference with +/// `AssocOp::fixity` is that an operator can be both left and right associative +/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`. +/// +/// Chained `as` and explicit `:` type coercion never need inner parenthesis so +/// they are considered +/// associative. +#[must_use] +fn associativity(op: &AssocOp) -> Associativity { + use rustc_ast::util::parser::AssocOp::{ + Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater, + GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract, + }; + + match *op { + Assign | AssignOp(_) => Associativity::Right, + Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both, + Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight + | Subtract => Associativity::Left, + DotDot | DotDotEq => Associativity::None, + } +} + +/// Converts a `hir::BinOp` to the corresponding assigning binary operator. +fn hirbinop2assignop(op: hir::BinOp) -> AssocOp { + use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star}; + + AssocOp::AssignOp(match op.node { + hir::BinOpKind::Add => Plus, + hir::BinOpKind::BitAnd => And, + hir::BinOpKind::BitOr => Or, + hir::BinOpKind::BitXor => Caret, + hir::BinOpKind::Div => Slash, + hir::BinOpKind::Mul => Star, + hir::BinOpKind::Rem => Percent, + hir::BinOpKind::Shl => Shl, + hir::BinOpKind::Shr => Shr, + hir::BinOpKind::Sub => Minus, + + hir::BinOpKind::And + | hir::BinOpKind::Eq + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt + | hir::BinOpKind::Le + | hir::BinOpKind::Lt + | hir::BinOpKind::Ne + | hir::BinOpKind::Or => panic!("This operator does not exist"), + }) +} + +/// Converts an `ast::BinOp` to the corresponding assigning binary operator. +fn astbinop2assignop(op: ast::BinOp) -> AssocOp { + use rustc_ast::ast::BinOpKind::{ + Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub, + }; + use rustc_ast::token::BinOpToken; + + AssocOp::AssignOp(match op.node { + Add => BinOpToken::Plus, + BitAnd => BinOpToken::And, + BitOr => BinOpToken::Or, + BitXor => BinOpToken::Caret, + Div => BinOpToken::Slash, + Mul => BinOpToken::Star, + Rem => BinOpToken::Percent, + Shl => BinOpToken::Shl, + Shr => BinOpToken::Shr, + Sub => BinOpToken::Minus, + And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"), + }) +} + +/// Returns the indentation before `span` if there are nothing but `[ \t]` +/// before it on its line. +fn indentation(cx: &T, span: Span) -> Option { + let lo = cx.sess().source_map().lookup_char_pos(span.lo()); + if let Some(line) = lo.file.get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */) { + if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') { + // We can mix char and byte positions here because we only consider `[ \t]`. + if lo.col == CharPos(pos) { + Some(line[..pos].into()) + } else { + None + } + } else { + None + } + } else { + None + } +} + +/// Convenience extension trait for `DiagnosticBuilder`. +pub trait DiagnosticBuilderExt<'a, T: LintContext> { + /// Suggests to add an attribute to an item. + /// + /// Correctly handles indentation of the attribute and item. + /// + /// # Example + /// + /// ```rust,ignore + /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]"); + /// ``` + fn suggest_item_with_attr( + &mut self, + cx: &T, + item: Span, + msg: &str, + attr: &D, + applicability: Applicability, + ); + + /// Suggest to add an item before another. + /// + /// The item should not be indented (expect for inner indentation). + /// + /// # Example + /// + /// ```rust,ignore + /// diag.suggest_prepend_item(cx, item, + /// "fn foo() { + /// bar(); + /// }"); + /// ``` + fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability); + + /// Suggest to completely remove an item. + /// + /// This will remove an item and all following whitespace until the next non-whitespace + /// character. This should work correctly if item is on the same indentation level as the + /// following item. + /// + /// # Example + /// + /// ```rust,ignore + /// diag.suggest_remove_item(cx, item, "remove this") + /// ``` + fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability); +} + +impl<'a, 'b, 'c, T: LintContext> DiagnosticBuilderExt<'c, T> for rustc_errors::DiagnosticBuilder<'b> { + fn suggest_item_with_attr( + &mut self, + cx: &T, + item: Span, + msg: &str, + attr: &D, + applicability: Applicability, + ) { + if let Some(indent) = indentation(cx, item) { + let span = item.with_hi(item.lo()); + + self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability); + } + } + + fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) { + if let Some(indent) = indentation(cx, item) { + let span = item.with_hi(item.lo()); + + let mut first = true; + let new_item = new_item + .lines() + .map(|l| { + if first { + first = false; + format!("{}\n", l) + } else { + format!("{}{}\n", indent, l) + } + }) + .collect::(); + + self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability); + } + } + + fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) { + let mut remove_span = item; + let hi = cx.sess().source_map().next_point(remove_span).hi(); + let fmpos = cx.sess().source_map().lookup_byte_offset(hi); + + if let Some(ref src) = fmpos.sf.src { + let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n'); + + if let Some(non_whitespace_offset) = non_whitespace_offset { + remove_span = remove_span + .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large"))) + } + } + + self.span_suggestion(remove_span, msg, String::new(), applicability); + } +} + +#[cfg(test)] +mod test { + use super::Sugg; + use std::borrow::Cow; + + const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()")); + + #[test] + fn make_return_transform_sugg_into_a_return_call() { + assert_eq!("return function_call()", SUGGESTION.make_return().to_string()); + } + + #[test] + fn blockify_transforms_sugg_into_a_block() { + assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string()); + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/sym.rs b/src/tools/clippy/clippy_lints/src/utils/sym.rs new file mode 100644 index 0000000000000..273288c3d52c5 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/sym.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! sym { + ($tt:tt) => { + rustc_span::symbol::Symbol::intern(stringify!($tt)) + }; +} diff --git a/src/tools/clippy/clippy_lints/src/utils/usage.rs b/src/tools/clippy/clippy_lints/src/utils/usage.rs new file mode 100644 index 0000000000000..c14da6aacea04 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/usage.rs @@ -0,0 +1,108 @@ +use crate::utils::match_var; +use rustc_ast::ast; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, HirId, Path}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_span::symbol::Ident; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase}; + +/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined. +pub fn mutated_variables<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &'a LateContext<'a, 'tcx>) -> Option> { + let mut delegate = MutVarsDelegate { + used_mutably: FxHashSet::default(), + skip: false, + }; + let def_id = expr.hir_id.owner.to_def_id(); + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new(&mut delegate, &infcx, def_id.expect_local(), cx.param_env, cx.tables).walk_expr(expr); + }); + + if delegate.skip { + return None; + } + Some(delegate.used_mutably) +} + +pub fn is_potentially_mutated<'a, 'tcx>( + variable: &'tcx Path<'_>, + expr: &'tcx Expr<'_>, + cx: &'a LateContext<'a, 'tcx>, +) -> bool { + if let Res::Local(id) = variable.res { + mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id)) + } else { + true + } +} + +struct MutVarsDelegate { + used_mutably: FxHashSet, + skip: bool, +} + +impl<'tcx> MutVarsDelegate { + #[allow(clippy::similar_names)] + fn update(&mut self, cat: &Place<'tcx>) { + match cat.base { + PlaceBase::Local(id) => { + self.used_mutably.insert(id); + }, + PlaceBase::Upvar(_) => { + //FIXME: This causes false negatives. We can't get the `NodeId` from + //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the + //`while`-body, not just the ones in the condition. + self.skip = true + }, + _ => {}, + } + } +} + +impl<'tcx> Delegate<'tcx> for MutVarsDelegate { + fn consume(&mut self, _: &Place<'tcx>, _: ConsumeMode) {} + + fn borrow(&mut self, cmt: &Place<'tcx>, bk: ty::BorrowKind) { + if let ty::BorrowKind::MutBorrow = bk { + self.update(&cmt) + } + } + + fn mutate(&mut self, cmt: &Place<'tcx>) { + self.update(&cmt) + } +} + +pub struct UsedVisitor { + pub var: ast::Name, // var to look for + pub used: bool, // has the var been used otherwise? +} + +impl<'tcx> Visitor<'tcx> for UsedVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if match_var(expr, self.var) { + self.used = true; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +pub fn is_unused<'tcx>(ident: &'tcx Ident, body: &'tcx Expr<'_>) -> bool { + let mut visitor = UsedVisitor { + var: ident.name, + used: false, + }; + walk_expr(&mut visitor, body); + !visitor.used +} diff --git a/src/tools/clippy/clippy_lints/src/vec.rs b/src/tools/clippy/clippy_lints/src/vec.rs new file mode 100644 index 0000000000000..1174f42157749 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/vec.rs @@ -0,0 +1,106 @@ +use crate::consts::constant; +use crate::utils::{higher, is_copy, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `&vec![..]` when using `&[..]` would + /// be possible. + /// + /// **Why is this bad?** This is less efficient. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// foo(&vec![1, 2]) + /// ``` + pub USELESS_VEC, + perf, + "useless `vec!`" +} + +declare_lint_pass!(UselessVec => [USELESS_VEC]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessVec { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + // search for `&vec![_]` expressions where the adjusted type is `&[_]` + if_chain! { + if let ty::Ref(_, ty, _) = cx.tables.expr_ty_adjusted(expr).kind; + if let ty::Slice(..) = ty.kind; + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref addressee) = expr.kind; + if let Some(vec_args) = higher::vec_macro(cx, addressee); + then { + check_vec_macro(cx, &vec_args, expr.span); + } + } + + // search for `for _ in vec![…]` + if_chain! { + if let Some((_, arg, _)) = higher::for_loop(expr); + if let Some(vec_args) = higher::vec_macro(cx, arg); + if is_copy(cx, vec_type(cx.tables.expr_ty_adjusted(arg))); + then { + // report the error around the `vec!` not inside `:` + let span = arg.span + .ctxt() + .outer_expn_data() + .call_site + .ctxt() + .outer_expn_data() + .call_site; + check_vec_macro(cx, &vec_args, span); + } + } + } +} + +fn check_vec_macro<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, vec_args: &higher::VecArgs<'tcx>, span: Span) { + let mut applicability = Applicability::MachineApplicable; + let snippet = match *vec_args { + higher::VecArgs::Repeat(elem, len) => { + if constant(cx, cx.tables, len).is_some() { + format!( + "&[{}; {}]", + snippet_with_applicability(cx, elem.span, "elem", &mut applicability), + snippet_with_applicability(cx, len.span, "len", &mut applicability) + ) + } else { + return; + } + }, + higher::VecArgs::Vec(args) => { + if let Some(last) = args.iter().last() { + let span = args[0].span.to(last.span); + + format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability)) + } else { + "&[]".into() + } + }, + }; + + span_lint_and_sugg( + cx, + USELESS_VEC, + span, + "useless use of `vec!`", + "you can use a slice directly", + snippet, + applicability, + ); +} + +/// Returns the item type of the vector (i.e., the `T` in `Vec`). +fn vec_type(ty: Ty<'_>) -> Ty<'_> { + if let ty::Adt(_, substs) = ty.kind { + substs.type_at(0) + } else { + panic!("The type of `vec!` is a not a struct?"); + } +} diff --git a/src/tools/clippy/clippy_lints/src/verbose_file_reads.rs b/src/tools/clippy/clippy_lints/src/verbose_file_reads.rs new file mode 100644 index 0000000000000..4d8d4438d881d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/verbose_file_reads.rs @@ -0,0 +1,85 @@ +use crate::utils::{match_type, paths, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for use of File::read_to_end and File::read_to_string. + /// + /// **Why is this bad?** `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. + /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust,no_run + /// # use std::io::Read; + /// # use std::fs::File; + /// let mut f = File::open("foo.txt").unwrap(); + /// let mut bytes = Vec::new(); + /// f.read_to_end(&mut bytes).unwrap(); + /// ``` + /// Can be written more concisely as + /// ```rust,no_run + /// # use std::fs; + /// let mut bytes = fs::read("foo.txt").unwrap(); + /// ``` + pub VERBOSE_FILE_READS, + restriction, + "use of `File::read_to_end` or `File::read_to_string`" +} + +declare_lint_pass!(VerboseFileReads => [VERBOSE_FILE_READS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for VerboseFileReads { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) { + if is_file_read_to_end(cx, expr) { + span_lint_and_help( + cx, + VERBOSE_FILE_READS, + expr.span, + "use of `File::read_to_end`", + None, + "consider using `fs::read` instead", + ); + } else if is_file_read_to_string(cx, expr) { + span_lint_and_help( + cx, + VERBOSE_FILE_READS, + expr.span, + "use of `File::read_to_string`", + None, + "consider using `fs::read_to_string` instead", + ) + } + } +} + +fn is_file_read_to_end<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let ExprKind::MethodCall(method_name, _, exprs) = expr.kind; + if method_name.ident.as_str() == "read_to_end"; + if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind; + let ty = cx.tables.expr_ty(&exprs[0]); + if match_type(cx, ty, &paths::FILE); + then { + return true + } + } + false +} + +fn is_file_read_to_string<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let ExprKind::MethodCall(method_name, _, exprs) = expr.kind; + if method_name.ident.as_str() == "read_to_string"; + if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind; + let ty = cx.tables.expr_ty(&exprs[0]); + if match_type(cx, ty, &paths::FILE); + then { + return true + } + } + false +} diff --git a/src/tools/clippy/clippy_lints/src/wildcard_dependencies.rs b/src/tools/clippy/clippy_lints/src/wildcard_dependencies.rs new file mode 100644 index 0000000000000..d8d48eb15358d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/wildcard_dependencies.rs @@ -0,0 +1,62 @@ +use crate::utils::{run_lints, span_lint}; +use rustc_hir::{hir_id::CRATE_HIR_ID, Crate}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::DUMMY_SP; + +use if_chain::if_chain; + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard dependencies in the `Cargo.toml`. + /// + /// **Why is this bad?** [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), + /// it is highly unlikely that you work with any possible version of your dependency, + /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```toml + /// [dependencies] + /// regex = "*" + /// ``` + pub WILDCARD_DEPENDENCIES, + cargo, + "wildcard dependencies being used" +} + +declare_lint_pass!(WildcardDependencies => [WILDCARD_DEPENDENCIES]); + +impl LateLintPass<'_, '_> for WildcardDependencies { + fn check_crate(&mut self, cx: &LateContext<'_, '_>, _: &Crate<'_>) { + if !run_lints(cx, &[WILDCARD_DEPENDENCIES], CRATE_HIR_ID) { + return; + } + + let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().no_deps().exec() { + metadata + } else { + span_lint(cx, WILDCARD_DEPENDENCIES, DUMMY_SP, "could not read cargo metadata"); + return; + }; + + for dep in &metadata.packages[0].dependencies { + // VersionReq::any() does not work + if_chain! { + if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); + if let Some(ref source) = dep.source; + if !source.starts_with("git"); + if dep.req == wildcard_ver; + then { + span_lint( + cx, + WILDCARD_DEPENDENCIES, + DUMMY_SP, + &format!("wildcard dependency for `{}`", dep.name), + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/wildcard_imports.rs b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs new file mode 100644 index 0000000000000..f3038861cee24 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs @@ -0,0 +1,156 @@ +use crate::utils::{in_macro, snippet, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + def::{DefKind, Res}, + Item, ItemKind, UseKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::BytePos; + +declare_clippy_lint! { + /// **What it does:** Checks for `use Enum::*`. + /// + /// **Why is this bad?** It is usually better style to use the prefixed name of + /// an enumeration variant, rather than importing variants. + /// + /// **Known problems:** Old-style enumerations that prefix the variants are + /// still around. + /// + /// **Example:** + /// ```rust + /// use std::cmp::Ordering::*; + /// ``` + pub ENUM_GLOB_USE, + pedantic, + "use items that import all variants of an enum" +} + +declare_clippy_lint! { + /// **What it does:** Checks for wildcard imports `use _::*`. + /// + /// **Why is this bad?** wildcard imports can polute the namespace. This is especially bad if + /// you try to import something through a wildcard, that already has been imported by name from + /// a different source: + /// + /// ```rust,ignore + /// use crate1::foo; // Imports a function named foo + /// use crate2::*; // Has a function named foo + /// + /// foo(); // Calls crate1::foo + /// ``` + /// + /// This can lead to confusing error messages at best and to unexpected behavior at worst. + /// + /// Note that this will not warn about wildcard imports from modules named `prelude`; many + /// crates (including the standard library) provide modules named "prelude" specifically + /// designed for wildcard import. + /// + /// **Known problems:** If macros are imported through the wildcard, this macro is not included + /// by the suggestion and has to be added by hand. + /// + /// Applying the suggestion when explicit imports of the things imported with a glob import + /// exist, may result in `unused_imports` warnings. + /// + /// **Example:** + /// + /// Bad: + /// ```rust,ignore + /// use crate1::*; + /// + /// foo(); + /// ``` + /// + /// Good: + /// ```rust,ignore + /// use crate1::foo; + /// + /// foo(); + /// ``` + pub WILDCARD_IMPORTS, + pedantic, + "lint `use _::*` statements" +} + +declare_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]); + +impl LateLintPass<'_, '_> for WildcardImports { + fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item<'_>) { + if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() { + return; + } + if_chain! { + if !in_macro(item.span); + if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind; + // don't lint prelude glob imports + if !use_path.segments.iter().last().map_or(false, |ps| ps.ident.as_str() == "prelude"); + let used_imports = cx.tcx.names_imported_by_glob_use(item.hir_id.owner); + if !used_imports.is_empty(); // Already handled by `unused_imports` + then { + let mut applicability = Applicability::MachineApplicable; + let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability); + let (span, braced_glob) = if import_source_snippet.is_empty() { + // This is a `_::{_, *}` import + // In this case `use_path.span` is empty and ends directly in front of the `*`, + // so we need to extend it by one byte. + ( + use_path.span.with_hi(use_path.span.hi() + BytePos(1)), + true, + ) + } else { + // In this case, the `use_path.span` ends right before the `::*`, so we need to + // extend it up to the `*`. Since it is hard to find the `*` in weird + // formattings like `use _ :: *;`, we extend it up to, but not including the + // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we + // can just use the end of the item span + let mut span = use_path.span.with_hi(item.span.hi()); + if snippet(cx, span, "").ends_with(';') { + span = use_path.span.with_hi(item.span.hi() - BytePos(1)); + } + ( + span, + false, + ) + }; + + let imports_string = if used_imports.len() == 1 { + used_imports.iter().next().unwrap().to_string() + } else { + let mut imports = used_imports + .iter() + .map(ToString::to_string) + .collect::>(); + imports.sort(); + if braced_glob { + imports.join(", ") + } else { + format!("{{{}}}", imports.join(", ")) + } + }; + + let sugg = if braced_glob { + imports_string + } else { + format!("{}::{}", import_source_snippet, imports_string) + }; + + let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res { + (ENUM_GLOB_USE, "usage of wildcard import for enum variants") + } else { + (WILDCARD_IMPORTS, "usage of wildcard import") + }; + + span_lint_and_sugg( + cx, + lint, + span, + message, + "try", + sugg, + applicability, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs new file mode 100644 index 0000000000000..5ad43ad55a36a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -0,0 +1,491 @@ +use std::borrow::Cow; +use std::ops::Range; + +use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then}; +use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle}; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_errors::Applicability; +use rustc_lexer::unescape::{self, EscapeError}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_parse::parser; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::Symbol; +use rustc_span::{BytePos, Span}; + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `println!("")` to + /// print a newline. + /// + /// **Why is this bad?** You should use `println!()`, which is simpler. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// println!(""); + /// ``` + pub PRINTLN_EMPTY_STRING, + style, + "using `println!(\"\")` with an empty string" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `print!()` with a format + /// string that + /// ends in a newline. + /// + /// **Why is this bad?** You should use `println!()` instead, which appends the + /// newline. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let name = "World"; + /// print!("Hello {}!\n", name); + /// ``` + /// use println!() instead + /// ```rust + /// # let name = "World"; + /// println!("Hello {}!", name); + /// ``` + pub PRINT_WITH_NEWLINE, + style, + "using `print!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// **What it does:** Checks for printing on *stdout*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// **Why is this bad?** People often print on *stdout* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// **Known problems:** Only catches `print!` and `println!` calls. + /// + /// **Example:** + /// ```rust + /// println!("Hello world!"); + /// ``` + pub PRINT_STDOUT, + restriction, + "printing on stdout" +} + +declare_clippy_lint! { + /// **What it does:** Checks for use of `Debug` formatting. The purpose of this + /// lint is to catch debugging remnants. + /// + /// **Why is this bad?** The purpose of the `Debug` trait is to facilitate + /// debugging Rust code. It should not be used in user-facing output. + /// + /// **Example:** + /// ```rust + /// # let foo = "bar"; + /// println!("{:?}", foo); + /// ``` + pub USE_DEBUG, + restriction, + "use of `Debug`-based formatting" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns about the use of literals as `print!`/`println!` args. + /// + /// **Why is this bad?** Using literals as `println!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// **Known problems:** Will also warn with macro calls as arguments that expand to literals + /// -- e.g., `println!("{}", env!("FOO"))`. + /// + /// **Example:** + /// ```rust + /// println!("{}", "foo"); + /// ``` + /// use the literal without formatting: + /// ```rust + /// println!("foo"); + /// ``` + pub PRINT_LITERAL, + style, + "printing a literal with a format string" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `writeln!(buf, "")` to + /// print a newline. + /// + /// **Why is this bad?** You should use `writeln!(buf)`, which is simpler. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, ""); + /// ``` + pub WRITELN_EMPTY_STRING, + style, + "using `writeln!(buf, \"\")` with an empty string" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns when you use `write!()` with a format + /// string that + /// ends in a newline. + /// + /// **Why is this bad?** You should use `writeln!()` instead, which appends the + /// newline. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// write!(buf, "Hello {}!\n", name); + /// ``` + pub WRITE_WITH_NEWLINE, + style, + "using `write!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// **What it does:** This lint warns about the use of literals as `write!`/`writeln!` args. + /// + /// **Why is this bad?** Using literals as `writeln!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// **Known problems:** Will also warn with macro calls as arguments that expand to literals + /// -- e.g., `writeln!(buf, "{}", env!("FOO"))`. + /// + /// **Example:** + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "{}", "foo"); + /// ``` + pub WRITE_LITERAL, + style, + "writing a literal with a format string" +} + +#[derive(Default)] +pub struct Write { + in_debug_impl: bool, +} + +impl_lint_pass!(Write => [ + PRINT_WITH_NEWLINE, + PRINTLN_EMPTY_STRING, + PRINT_STDOUT, + USE_DEBUG, + PRINT_LITERAL, + WRITE_WITH_NEWLINE, + WRITELN_EMPTY_STRING, + WRITE_LITERAL +]); + +impl EarlyLintPass for Write { + fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) { + if let ItemKind::Impl { + of_trait: Some(trait_ref), + .. + } = &item.kind + { + let trait_name = trait_ref + .path + .segments + .iter() + .last() + .expect("path has at least one segment") + .ident + .name; + if trait_name == sym!(Debug) { + self.in_debug_impl = true; + } + } + } + + fn check_item_post(&mut self, _: &EarlyContext<'_>, _: &Item) { + self.in_debug_impl = false; + } + + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) { + if mac.path == sym!(println) { + span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`"); + if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) { + if fmt_str.symbol == Symbol::intern("") { + span_lint_and_sugg( + cx, + PRINTLN_EMPTY_STRING, + mac.span(), + "using `println!(\"\")`", + "replace it with", + "println!()".to_string(), + Applicability::MachineApplicable, + ); + } + } + } else if mac.path == sym!(print) { + span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`"); + if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) { + if check_newlines(&fmt_str) { + span_lint_and_then( + cx, + PRINT_WITH_NEWLINE, + mac.span(), + "using `print!()` with a format string that ends in a single newline", + |err| { + err.multipart_suggestion( + "use `println!` instead", + vec![ + (mac.path.span, String::from("println")), + (newline_span(&fmt_str), String::new()), + ], + Applicability::MachineApplicable, + ); + }, + ); + } + } + } else if mac.path == sym!(write) { + if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), true) { + if check_newlines(&fmt_str) { + span_lint_and_then( + cx, + WRITE_WITH_NEWLINE, + mac.span(), + "using `write!()` with a format string that ends in a single newline", + |err| { + err.multipart_suggestion( + "use `writeln!()` instead", + vec![ + (mac.path.span, String::from("writeln")), + (newline_span(&fmt_str), String::new()), + ], + Applicability::MachineApplicable, + ); + }, + ) + } + } + } else if mac.path == sym!(writeln) { + if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) { + if fmt_str.symbol == Symbol::intern("") { + let mut applicability = Applicability::MachineApplicable; + let suggestion = expr.map_or_else( + move || { + applicability = Applicability::HasPlaceholders; + Cow::Borrowed("v") + }, + move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability), + ); + + span_lint_and_sugg( + cx, + WRITELN_EMPTY_STRING, + mac.span(), + format!("using `writeln!({}, \"\")`", suggestion).as_str(), + "replace it with", + format!("writeln!({})", suggestion), + applicability, + ); + } + } + } + } +} + +/// Given a format string that ends in a newline and its span, calculates the span of the +/// newline. +fn newline_span(fmtstr: &StrLit) -> Span { + let sp = fmtstr.span; + let contents = &fmtstr.symbol.as_str(); + + let newline_sp_hi = sp.hi() + - match fmtstr.style { + StrStyle::Cooked => BytePos(1), + StrStyle::Raw(hashes) => BytePos((1 + hashes).into()), + }; + + let newline_sp_len = if contents.ends_with('\n') { + BytePos(1) + } else if contents.ends_with(r"\n") { + BytePos(2) + } else { + panic!("expected format string to contain a newline"); + }; + + sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi) +} + +impl Write { + /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two + /// `Option`s. The first `Option` of the tuple is the macro's format string. It includes + /// the contents of the string, whether it's a raw string, and the span of the literal in the + /// source. The second `Option` in the tuple is, in the `write[ln]!` case, the expression the + /// `format_str` should be written to. + /// + /// Example: + /// + /// Calling this function on + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let something = "something"; + /// writeln!(buf, "string to write: {}", something); + /// ``` + /// will return + /// ```rust,ignore + /// (Some("string to write: {}"), Some(buf)) + /// ``` + #[allow(clippy::too_many_lines)] + fn check_tts<'a>( + &self, + cx: &EarlyContext<'a>, + tts: &TokenStream, + is_write: bool, + ) -> (Option, Option) { + use fmt_macros::{ + AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, Parser, Piece, + }; + let tts = tts.clone(); + + let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None); + let mut expr: Option = None; + if is_write { + expr = match parser.parse_expr().map_err(|mut err| err.cancel()) { + Ok(p) => Some(p.into_inner()), + Err(_) => return (None, None), + }; + // might be `writeln!(foo)` + if parser.expect(&token::Comma).map_err(|mut err| err.cancel()).is_err() { + return (None, expr); + } + } + + let fmtstr = match parser.parse_str_lit() { + Ok(fmtstr) => fmtstr, + Err(_) => return (None, expr), + }; + let tmp = fmtstr.symbol.as_str(); + let mut args = vec![]; + let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false); + while let Some(piece) = fmt_parser.next() { + if !fmt_parser.errors.is_empty() { + return (None, expr); + } + if let Piece::NextArgument(arg) = piece { + if !self.in_debug_impl && arg.format.ty == "?" { + // FIXME: modify rustc's fmt string parser to give us the current span + span_lint(cx, USE_DEBUG, parser.prev_token.span, "use of `Debug`-based formatting"); + } + args.push(arg); + } + } + let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL }; + let mut idx = 0; + loop { + const SIMPLE: FormatSpec<'_> = FormatSpec { + fill: None, + align: AlignUnknown, + flags: 0, + precision: CountImplied, + precision_span: None, + width: CountImplied, + width_span: None, + ty: "", + ty_span: None, + }; + if !parser.eat(&token::Comma) { + return (Some(fmtstr), expr); + } + let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) { + expr + } else { + return (Some(fmtstr), None); + }; + match &token_expr.kind { + ExprKind::Lit(_) => { + let mut all_simple = true; + let mut seen = false; + for arg in &args { + match arg.position { + ArgumentImplicitlyIs(n) | ArgumentIs(n) => { + if n == idx { + all_simple &= arg.format == SIMPLE; + seen = true; + } + }, + ArgumentNamed(_) => {}, + } + } + if all_simple && seen { + span_lint(cx, lint, token_expr.span, "literal with an empty format string"); + } + idx += 1; + }, + ExprKind::Assign(lhs, rhs, _) => { + if let ExprKind::Lit(_) = rhs.kind { + if let ExprKind::Path(_, p) = &lhs.kind { + let mut all_simple = true; + let mut seen = false; + for arg in &args { + match arg.position { + ArgumentImplicitlyIs(_) | ArgumentIs(_) => {}, + ArgumentNamed(name) => { + if *p == name { + seen = true; + all_simple &= arg.format == SIMPLE; + } + }, + } + } + if all_simple && seen { + span_lint(cx, lint, rhs.span, "literal with an empty format string"); + } + } + } + }, + _ => idx += 1, + } + } + } +} + +/// Checks if the format string contains a single newline that terminates it. +/// +/// Literal and escaped newlines are both checked (only literal for raw strings). +fn check_newlines(fmtstr: &StrLit) -> bool { + let mut has_internal_newline = false; + let mut last_was_cr = false; + let mut should_lint = false; + + let contents = &fmtstr.symbol.as_str(); + + let mut cb = |r: Range, c: Result| { + let c = c.unwrap(); + + if r.end == contents.len() && c == '\n' && !last_was_cr && !has_internal_newline { + should_lint = true; + } else { + last_was_cr = c == '\r'; + if c == '\n' { + has_internal_newline = true; + } + } + }; + + match fmtstr.style { + StrStyle::Cooked => unescape::unescape_str(contents, &mut cb), + StrStyle::Raw(_) => unescape::unescape_raw_str(contents, &mut cb), + } + + should_lint +} diff --git a/src/tools/clippy/clippy_lints/src/zero_div_zero.rs b/src/tools/clippy/clippy_lints/src/zero_div_zero.rs new file mode 100644 index 0000000000000..fb4700d8743fd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/zero_div_zero.rs @@ -0,0 +1,61 @@ +use crate::consts::{constant_simple, Constant}; +use crate::utils::span_lint_and_help; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `0.0 / 0.0`. + /// + /// **Why is this bad?** It's less readable than `f32::NAN` or `f64::NAN`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// 0.0f32 / 0.0; + /// ``` + pub ZERO_DIVIDED_BY_ZERO, + complexity, + "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`" +} + +declare_lint_pass!(ZeroDiv => [ZERO_DIVIDED_BY_ZERO]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ZeroDiv { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + // check for instances of 0.0/0.0 + if_chain! { + if let ExprKind::Binary(ref op, ref left, ref right) = expr.kind; + if let BinOpKind::Div = op.node; + // TODO - constant_simple does not fold many operations involving floats. + // That's probably fine for this lint - it's pretty unlikely that someone would + // do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too. + if let Some(lhs_value) = constant_simple(cx, cx.tables, left); + if let Some(rhs_value) = constant_simple(cx, cx.tables, right); + if Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value; + if Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value; + then { + // since we're about to suggest a use of f32::NAN or f64::NAN, + // match the precision of the literals that are given. + let float_type = match (lhs_value, rhs_value) { + (Constant::F64(_), _) + | (_, Constant::F64(_)) => "f64", + _ => "f32" + }; + span_lint_and_help( + cx, + ZERO_DIVIDED_BY_ZERO, + expr.span, + "constant division of `0.0` with `0.0` will always result in NaN", + None, + &format!( + "Consider using `{}::NAN` if you would like a constant representing NaN", + float_type, + ), + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_workspace_tests/Cargo.toml b/src/tools/clippy/clippy_workspace_tests/Cargo.toml new file mode 100644 index 0000000000000..7a235b215d38d --- /dev/null +++ b/src/tools/clippy/clippy_workspace_tests/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "clippy_workspace_tests" +version = "0.1.0" +edition = "2018" + +[workspace] +members = ["subcrate"] diff --git a/src/tools/clippy/clippy_workspace_tests/src/main.rs b/src/tools/clippy/clippy_workspace_tests/src/main.rs new file mode 100644 index 0000000000000..b322eca1db51d --- /dev/null +++ b/src/tools/clippy/clippy_workspace_tests/src/main.rs @@ -0,0 +1,3 @@ +#![deny(rust_2018_idioms)] + +fn main() {} diff --git a/src/tools/clippy/clippy_workspace_tests/subcrate/Cargo.toml b/src/tools/clippy/clippy_workspace_tests/subcrate/Cargo.toml new file mode 100644 index 0000000000000..83ea5868160bb --- /dev/null +++ b/src/tools/clippy/clippy_workspace_tests/subcrate/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "subcrate" +version = "0.1.0" diff --git a/src/tools/clippy/clippy_workspace_tests/subcrate/src/lib.rs b/src/tools/clippy/clippy_workspace_tests/subcrate/src/lib.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/src/tools/clippy/clippy_workspace_tests/subcrate/src/lib.rs @@ -0,0 +1 @@ + diff --git a/src/tools/clippy/doc/adding_lints.md b/src/tools/clippy/doc/adding_lints.md new file mode 100644 index 0000000000000..9ad1315c17521 --- /dev/null +++ b/src/tools/clippy/doc/adding_lints.md @@ -0,0 +1,477 @@ +# Adding a new lint + +You are probably here because you want to add a new lint to Clippy. If this is +the first time you're contributing to Clippy, this document guides you through +creating an example lint from scratch. + +To get started, we will create a lint that detects functions called `foo`, +because that's clearly a non-descriptive name. + +- [Adding a new lint](#adding-a-new-lint) + - [Setup](#setup) + - [Getting Started](#getting-started) + - [Testing](#testing) + - [Rustfix tests](#rustfix-tests) + - [Edition 2018 tests](#edition-2018-tests) + - [Testing manually](#testing-manually) + - [Lint declaration](#lint-declaration) + - [Lint passes](#lint-passes) + - [Emitting a lint](#emitting-a-lint) + - [Adding the lint logic](#adding-the-lint-logic) + - [Author lint](#author-lint) + - [Documentation](#documentation) + - [Running rustfmt](#running-rustfmt) + - [Debugging](#debugging) + - [PR Checklist](#pr-checklist) + - [Cheatsheet](#cheatsheet) + +## Setup + +When working on Clippy, you will need the current git master version of rustc, +which can change rapidly. Make sure you're working near rust-clippy's master, +and use the `setup-toolchain.sh` script to configure the appropriate toolchain +for the Clippy directory. + +## Getting Started + +There is a bit of boilerplate code that needs to be set up when creating a new +lint. Fortunately, you can use the clippy dev tools to handle this for you. We +are naming our new lint `foo_functions` (lints are generally written in snake +case), and we don't need type information so it will have an early pass type +(more on this later on). To get started on this lint you can run +`cargo dev new_lint --name=foo_functions --pass=early --category=pedantic` +(category will default to nursery if not provided). This command will create +two files: `tests/ui/foo_functions.rs` and `clippy_lints/src/foo_functions.rs`, +as well as run `cargo dev update_lints` to register the new lint. Next, we'll +open up these files and add our lint! + +## Testing + +Let's write some tests first that we can execute while we iterate on our lint. + +Clippy uses UI tests for testing. UI tests check that the output of Clippy is +exactly as expected. Each test is just a plain Rust file that contains the code +we want to check. The output of Clippy is compared against a `.stderr` file. +Note that you don't have to create this file yourself, we'll get to +generating the `.stderr` files further down. + +We start by opening the test file created at `tests/ui/foo_functions.rs`. + +Update the file with some examples to get started: + +```rust +#![warn(clippy::foo_functions)] + +// Impl methods +struct A; +impl A { + pub fn fo(&self) {} + pub fn foo(&self) {} + pub fn food(&self) {} +} + +// Default trait methods +trait B { + fn fo(&self) {} + fn foo(&self) {} + fn food(&self) {} +} + +// Plain functions +fn fo() {} +fn foo() {} +fn food() {} + +fn main() { + // We also don't want to lint method calls + foo(); + let a = A; + a.foo(); +} +``` + +Now we can run the test with `TESTNAME=foo_functions cargo uitest`, +currently this test is meaningless though. + +While we are working on implementing our lint, we can keep running the UI +test. That allows us to check if the output is turning into what we want. + +Once we are satisfied with the output, we need to run +`tests/ui/update-all-references.sh` to update the `.stderr` file for our lint. +Please note that, we should run `TESTNAME=foo_functions cargo uitest` +every time before running `tests/ui/update-all-references.sh`. +Running `TESTNAME=foo_functions cargo uitest` should pass then. When we commit +our lint, we need to commit the generated `.stderr` files, too. In general, you +should only commit files changed by `tests/ui/update-all-references.sh` for the +specific lint you are creating/editing. + +## Rustfix tests + +If the lint you are working on is making use of structured suggestions, the +test file should include a `// run-rustfix` comment at the top. This will +additionally run [rustfix] for that test. Rustfix will apply the suggestions +from the lint to the code of the test file and compare that to the contents of +a `.fixed` file. + +Use `tests/ui/update-all-references.sh` to automatically generate the +`.fixed` file after running the tests. + +[rustfix]: https://github.com/rust-lang/rustfix + +## Edition 2018 tests + +Some features require the 2018 edition to work (e.g. `async_await`), but +compile-test tests run on the 2015 edition by default. To change this behavior +add `// edition:2018` at the top of the test file (note that it's space-sensitive). + +## Testing manually + +Manually testing against an example file can be useful if you have added some +`println!`s and the test suite output becomes unreadable. To try Clippy with +your local modifications, run `env CLIPPY_TESTS=true cargo run --bin +clippy-driver -- -L ./target/debug input.rs` from the working copy root. + +With tests in place, let's have a look at implementing our lint now. + +## Lint declaration + +Let's start by opening the new file created in the `clippy_lints` crate +at `clippy_lints/src/foo_functions.rs`. That's the crate where all the +lint code is. This file has already imported some initial things we will need: + +```rust +use rustc_lint::{EarlyLintPass, EarlyContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_ast::ast::*; +``` + +The next step is to update the lint declaration. Lints are declared using the +[`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update +the auto-generated lint declaration to have a real description, something like this: + +```rust +declare_clippy_lint! { + /// **What it does:** + /// + /// **Why is this bad?** + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code + /// ``` + pub FOO_FUNCTIONS, + pedantic, + "function named `foo`, which is not a descriptive name" +} +``` + +* The section of lines prefixed with `///` constitutes the lint documentation + section. This is the default documentation style and will be displayed + [like this][example_lint_page]. +* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the + [lint naming guidelines][lint_naming] here when naming your lint. + In short, the name should state the thing that is being checked for and + read well when used with `allow`/`warn`/`deny`. +* `pedantic` sets the lint level to `Allow`. + The exact mapping can be found [here][category_level_mapping] +* The last part should be a text that explains what exactly is wrong with the + code + +The rest of this file contains an empty implementation for our lint pass, +which in this case is `EarlyLintPass` and should look like this: + +```rust +// clippy_lints/src/foo_functions.rs + +// .. imports and lint declaration .. + +declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]); + +impl EarlyLintPass for FooFunctions {} +``` + +Normally after declaring the lint, we have to run `cargo dev update_lints`, +which updates some files, so Clippy knows about the new lint. Since we used +`cargo dev new_lint ...` to generate the lint declaration, this was done +automatically. While `update_lints` automates most of the things, it doesn't +automate everything. We will have to register our lint pass manually in the +`register_plugins` function in `clippy_lints/src/lib.rs`: + +```rust +store.register_early_pass(|| box foo_functions::FooFunctions); +``` + +[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60 +[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure +[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints +[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110 + +## Lint passes + +Writing a lint that only checks for the name of a function means that we only +have to deal with the AST and don't have to deal with the type system at all. +This is good, because it makes writing this particular lint less complicated. + +We have to make this decision with every new Clippy lint. It boils down to using +either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass]. + +In short, the `LateLintPass` has access to type information while the +`EarlyLintPass` doesn't. If you don't need access to type information, use the +`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed +hasn't really been a concern with Clippy so far. + +Since we don't need type information for checking the function name, we used +`--pass=early` when running the new lint automation and all the imports were +added accordingly. + +[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html +[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html + +## Emitting a lint + +With UI tests and the lint declaration in place, we can start working on the +implementation of the lint logic. + +Let's start by implementing the `EarlyLintPass` for our `FooFunctions`: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + // TODO: Emit lint here + } +} +``` + +We implement the [`check_fn`][check_fn] method from the +[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various +information about the function that is currently being checked. More on that in +the next section. Let's worry about the details later and emit our lint for +*every* function definition first. + +Depending on how complex we want our lint message to be, we can choose from a +variety of lint emission functions. They can all be found in +[`clippy_lints/src/utils/diagnostics.rs`][diagnostics]. + +`span_lint_and_help` seems most appropriate in this case. It allows us to +provide an extra help message and we can't really suggest a better name +automatically. This is how it looks: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + span_lint_and_help( + cx, + FOO_FUNCTIONS, + span, + "function named `foo`", + None, + "consider using a more meaningful name" + ); + } +} +``` + +Running our UI test should now produce output that contains the lint message. + +[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn +[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/diagnostics.rs + +## Adding the lint logic + +Writing the logic for your lint will most likely be different from our example, +so this section is kept rather short. + +Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind] +that has the [`FnKind::Fn`] variant. It provides access to the name of the +function/method via an [`Ident`][ident]. + +With that we can expand our `check_fn` method to: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + if is_foo_fn(fn_kind) { + span_lint_and_help( + cx, + FOO_FUNCTIONS, + span, + "function named `foo`", + None, + "consider using a more meaningful name" + ); + } + } +} +``` + +We separate the lint conditional from the lint emissions because it makes the +code a bit easier to read. In some cases this separation would also allow to +write some unit tests (as opposed to only UI tests) for the separate function. + +In our example, `is_foo_fn` looks like: + +```rust +// use statements, impl EarlyLintPass, check_fn, .. + +fn is_foo_fn(fn_kind: FnKind<'_>) -> bool { + match fn_kind { + FnKind::Fn(_, ident, ..) => { + // check if `fn` name is `foo` + ident.name.as_str() == "foo" + } + // ignore closures + FnKind::Closure(..) => false + } +} +``` + +Now we should also run the full test suite with `cargo test`. At this point +running `cargo test` should produce the expected output. Remember to run +`tests/ui/update-all-references.sh` to update the `.stderr` file. + +`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint +implementation is not violating any Clippy lints itself. + +That should be it for the lint implementation. Running `cargo test` should now +pass. + +[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html +[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn +[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html + +## Author lint + +If you have trouble implementing your lint, there is also the internal `author` +lint to generate Clippy code that detects the offending pattern. It does not +work for all of the Rust syntax, but can give a good starting point. + +The quickest way to use it, is the +[Rust playground: play.rust-lang.org][author_example]. +Put the code you want to lint into the editor and add the `#[clippy::author]` +attribute above the item. Then run Clippy via `Tools -> Clippy` and you should +see the generated code in the output below. + +[Here][author_example] is an example on the playground. + +If the command was executed successfully, you can copy the code over to where +you are implementing your lint. + +[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55 + +## Documentation + +The final thing before submitting our PR is to add some documentation to our +lint declaration. + +Please document your lint with a doc comment akin to the following: + +```rust +declare_clippy_lint! { + /// **What it does:** Checks for ... (describe what the lint matches). + /// + /// **Why is this bad?** Supply the reason for linting the code. + /// + /// **Known problems:** None. (Or describe where it could go wrong.) + /// + /// **Example:** + /// + /// ```rust,ignore + /// // Bad + /// Insert a short example of code that triggers the lint + /// + /// // Good + /// Insert a short example of improved code that doesn't trigger the lint + /// ``` + pub FOO_FUNCTIONS, + pedantic, + "function named `foo`, which is not a descriptive name" +} +``` + +Once your lint is merged, this documentation will show up in the [lint +list][lint_list]. + +[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html + +## Running rustfmt + +[Rustfmt] is a tool for formatting Rust code according to style guidelines. +Your code has to be formatted by `rustfmt` before a PR can be merged. +Clippy uses nightly `rustfmt` in the CI. + +It can be installed via `rustup`: + +```bash +rustup component add rustfmt --toolchain=nightly +``` + +Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is +installed for the nightly toolchain. + +[Rustfmt]: https://github.com/rust-lang/rustfmt + +## Debugging + +If you want to debug parts of your lint implementation, you can use the [`dbg!`] +macro anywhere in your code. Running the tests should then include the debug +output in the `stdout` part. + +[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html + +## PR Checklist + +Before submitting your PR make sure you followed all of the basic requirements: + + + +- [ ] Followed [lint naming conventions][lint_naming] +- [ ] Added passing UI tests (including committed `.stderr` file) +- [ ] `cargo test` passes locally +- [ ] Executed `cargo dev update_lints` +- [ ] Added lint documentation +- [ ] Run `cargo dev fmt` + +## Cheatsheet + +Here are some pointers to things you are likely going to need for every lint: + +* [Clippy utils][utils] - Various helper functions. Maybe the function you need + is already in here (`implements_trait`, `match_path`, `snippet`, etc) +* [Clippy diagnostics][diagnostics] +* [The `if_chain` macro][if_chain] +* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro] +* [`Span`][span] +* [`Applicability`][applicability] +* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler concepts +* [The nightly rustc docs][nightly_docs] which has been linked to throughout + this guide + +For `EarlyLintPass` lints: + +* [`EarlyLintPass`][early_lint_pass] +* [`rustc_ast::ast`][ast] + +For `LateLintPass` lints: + +* [`LateLintPass`][late_lint_pass] +* [`Ty::TyKind`][ty] + +While most of Clippy's lint utils are documented, most of rustc's internals lack +documentation currently. This is unfortunate, but in most cases you can probably +get away with copying things from existing similar lints. If you are stuck, +don't hesitate to ask on [Discord] or in the issue/PR. + +[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/mod.rs +[if_chain]: https://docs.rs/if_chain/*/if_chain/ +[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion +[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html +[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html +[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html +[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/ +[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ +[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html +[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html +[Discord]: https://discord.gg/rust-lang diff --git a/src/tools/clippy/doc/backport.md b/src/tools/clippy/doc/backport.md new file mode 100644 index 0000000000000..259696658eaba --- /dev/null +++ b/src/tools/clippy/doc/backport.md @@ -0,0 +1,50 @@ +# Backport Changes + +Sometimes it is necessary to backport changes to the beta release of Clippy. +Backports in Clippy are rare and should be approved by the Clippy team. For +example, a backport is done, if a crucial ICE was fixed or a lint is broken to a +point, that it has to be disabled, before landing on stable. + +Backports are done to the `beta` release of Clippy. Backports to stable Clippy +releases basically don't exist, since this would require a Rust point release, +which is almost never justifiable for a Clippy fix. + + +## Backport the changes + +Backports are done on the beta branch of the Clippy repository. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout beta +$ git checkout -b backport +$ git cherry-pick # `` is the commit hash of the commit, that should be backported +$ git push origin backport +``` + +After this, you can open a PR to the `beta` branch of the Clippy repository. + + +## Update Clippy in the Rust Repository + +This step must be done, **after** the PR of the previous step was merged. + +After the backport landed in the Clippy repository, also the Clippy version on +the Rust `beta` branch has to be updated. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git checkout beta +$ git checkout -b clippy_backport +$ pushd src/tools/clippy +$ git fetch +$ git checkout beta +$ popd +$ git add src/tools/clippy +§ git commit -m "Update Clippy" +$ git push origin clippy_backport +``` + +After this you can open a PR to the `beta` branch of the Rust repository. In +this PR you should tag the Clippy team member, that agreed to the backport or +the `@rust-lang/clippy` team. Make sure to add `[beta]` to the title of the PR. diff --git a/src/tools/clippy/doc/changelog_update.md b/src/tools/clippy/doc/changelog_update.md new file mode 100644 index 0000000000000..0b80cce6d23ea --- /dev/null +++ b/src/tools/clippy/doc/changelog_update.md @@ -0,0 +1,78 @@ +# Changelog Update + +If you want to help with updating the [changelog][changelog], you're in the right place. + +## When to update + +Typos and other small fixes/additions are _always_ welcome. + +Special care needs to be taken when it comes to updating the changelog for a new +Rust release. For that purpose, the changelog is ideally updated during the week +before an upcoming stable release. You can find the release dates on the [Rust +Forge][forge]. + +Most of the time we only need to update the changelog for minor Rust releases. It's +been very rare that Clippy changes were included in a patch release. + +## Changelog update walkthrough + +### 1. Finding the relevant Clippy commits + +Each Rust release ships with its own version of Clippy. The Clippy submodule can +be found in the `tools` directory of the Rust repository. + +Depending on the current time and what exactly you want to update, the following +bullet points might be helpful: + +* When writing the release notes for the **upcoming stable release** you need to check + out the Clippy commit of the current Rust `beta` branch. [Link][rust_beta_tools] +* When writing the release notes for the **upcoming beta release**, you need to check + out the Clippy commit of the current Rust `master`. [Link][rust_master_tools] +* When writing the (forgotten) release notes for a **past stable release**, you + need to select the Rust release tag from the dropdown and then check the + commit of the Clippy directory: + + ![Explanation of how to find the commit hash](https://user-images.githubusercontent.com/2042399/62846160-1f8b0480-bcce-11e9-9da8-7964ca034e7a.png) + + +### 2. Fetching the PRs between those commits + +Once you've got the correct commit range, run + + util/fetch_prs_between.sh commit1 commit2 > changes.txt + +and open that 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 +specified. It should have filtered out most of the irrelevant PRs +already, but it's a good idea to do a manual cleanup pass where you look for +more irrelevant PRs. If you're not sure about some PRs, just leave them in for +the review and ask for feedback. + +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. Suggestion fixes/improvements +6. ICE fixes +7. Documentation improvements +8. Others + +Please also be sure to update the Beta/Unreleased sections at the top with the +relevant commit ranges. + +[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md +[forge]: https://forge.rust-lang.org/ +[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools +[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools diff --git a/src/tools/clippy/doc/release.md b/src/tools/clippy/doc/release.md new file mode 100644 index 0000000000000..9d69fa8a7f69a --- /dev/null +++ b/src/tools/clippy/doc/release.md @@ -0,0 +1,111 @@ +# Release a new Clippy Version + +_NOTE: This document is probably only relevant to you, if you're a member of the +Clippy team._ + +Clippy is released together with stable Rust releases. The dates for these +releases can be found at the [Rust Forge]. This document explains the necessary +steps to create a Clippy release. + +1. [Find the Clippy commit](#find-the-clippy-commit) +2. [Tag the stable commit](#tag-the-stable-commit) +3. [Update `CHANGELOG.md`](#update-changelogmd) +4. [Remerge the `beta` branch](#remerge-the-beta-branch) +5. [Update the `beta` branch](#update-the-beta-branch) + +_NOTE: This document is for stable Rust releases, not for point releases. For +point releases, step 1. and 2. should be enough._ + +[Rust Forge]: https://forge.rust-lang.org/ + + +## Find the Clippy commit + +The first step is to tag the Clippy commit, that is included in the stable Rust +release. This commit can be found in the Rust repository. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git fetch upstream # `upstream` is the `rust-lang/rust` remote +$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version +$ git submodule update +$ SHA=$(git submodule status src/tools/clippy | awk '{print $1}') +``` + + +## Tag the stable commit + +After finding the Clippy commit, it can be tagged with the release number. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout $SHA +$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version +$ git push upstream master --tags # `upstream` is the `rust-lang/rust-clippy` remote +``` + +After this, the release should be available on the Clippy [release page]. + +[release page]: https://github.com/rust-lang/rust-clippy/releases + + +## Update `CHANGELOG.md` + +For this see the document on [how to update the changelog]. + +[how to update the changelog]: https://github.com/rust-lang/rust-clippy/blob/master/doc/changelog_update.md + + +## Remerge the `beta` branch + +This step is only necessary, if since the last release something was backported +to the beta Rust release. The remerge is then necessary, to make sure that the +Clippy commit, that was used by the now stable Rust release, persists in the +tree of the Clippy repository. + +To find out if this step is necessary run + +```bash +# Assumes that the local master branch is up-to-date +$ git fetch upstream +$ git branch master --contains upstream/beta +``` + +If this command outputs `master`, this step is **not** necessary. + +```bash +# Assuming `HEAD` is the current `master` branch of rust-lang/rust-clippy +$ git checkout -b backport_remerge +$ git merge beta +$ git diff # This diff has to be empty, otherwise something with the remerge failed +$ git push origin backport_remerge # This can be pushed to your fork +``` + +After this, open a PR to the master branch. In this PR, the commit hash of the +`HEAD` of the `beta` branch must exists. In addition to that, no files should +be changed by this PR. + + +## Update the `beta` branch + +This step must be done **after** the PR of the previous step was merged. + +First, the Clippy commit of the `beta` branch of the Rust repository has to be +determined. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git checkout beta +$ git submodule update +$ BETA_SHA=$(git submodule status src/tools/clippy | awk '{print $1}') +``` + +After finding the Clippy commit, the `beta` branch in the Clippy repository can +be updated. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout beta +$ git rebase $BETA_SHA +$ git push upstream beta +``` diff --git a/src/tools/clippy/etc/relicense/RELICENSE_DOCUMENTATION.md b/src/tools/clippy/etc/relicense/RELICENSE_DOCUMENTATION.md new file mode 100644 index 0000000000000..fcd7abbf3f169 --- /dev/null +++ b/src/tools/clippy/etc/relicense/RELICENSE_DOCUMENTATION.md @@ -0,0 +1,69 @@ +This repository was previously licensed under MPL-2.0, however in #3093 +([archive](http://web.archive.org/web/20181005185227/https://github.com/rust-lang-nursery/rust-clippy/issues/3093), +[screenshot](https://user-images.githubusercontent.com/1617736/46573505-5b856880-c94b-11e8-9a14-981c889b4981.png)) we +relicensed it to the Rust license (dual licensed as Apache v2 / MIT) + +At the time, the contributors were those listed in contributors.txt. + +We opened a bunch of issues asking for an explicit relicensing approval. Screenshots of all these issues at the time of +relicensing are archived on GitHub. We also have saved Wayback Machine copies of these: + +- #3094 + ([archive](http://web.archive.org/web/20181005191247/https://github.com/rust-lang-nursery/rust-clippy/issues/3094), + [screenshot](https://user-images.githubusercontent.com/1617736/46573506-5b856880-c94b-11e8-8a44-51cb40bc16ee.png)) +- #3095 + ([archive](http://web.archive.org/web/20181005184416/https://github.com/rust-lang-nursery/rust-clippy/issues/3095), + [screenshot](https://user-images.githubusercontent.com/1617736/46573507-5c1dff00-c94b-11e8-912a-4bd6b5f838f5.png)) +- #3096 + ([archive](http://web.archive.org/web/20181005184802/https://github.com/rust-lang-nursery/rust-clippy/issues/3096), + [screenshot](https://user-images.githubusercontent.com/1617736/46573508-5c1dff00-c94b-11e8-9425-2464f7260ff0.png)) +- #3097 + ([archive](http://web.archive.org/web/20181005184821/https://github.com/rust-lang-nursery/rust-clippy/issues/3097), + [screenshot](https://user-images.githubusercontent.com/1617736/46573509-5c1dff00-c94b-11e8-8ba2-53f687984fe7.png)) +- #3098 + ([archive](http://web.archive.org/web/20181005184900/https://github.com/rust-lang-nursery/rust-clippy/issues/3098), + [screenshot](https://user-images.githubusercontent.com/1617736/46573510-5c1dff00-c94b-11e8-8f64-371698401c60.png)) +- #3099 + ([archive](http://web.archive.org/web/20181005184901/https://github.com/rust-lang-nursery/rust-clippy/issues/3099), + [screenshot](https://user-images.githubusercontent.com/1617736/46573511-5c1dff00-c94b-11e8-8e20-7d0eeb392b95.png)) +- #3100 + ([archive](http://web.archive.org/web/20181005184901/https://github.com/rust-lang-nursery/rust-clippy/issues/3100), + [screenshot](https://user-images.githubusercontent.com/1617736/46573512-5c1dff00-c94b-11e8-8a13-7d758ed3563d.png)) +- #3230 + ([archive](http://web.archive.org/web/20181005184903/https://github.com/rust-lang-nursery/rust-clippy/issues/3230), + [screenshot](https://user-images.githubusercontent.com/1617736/46573513-5cb69580-c94b-11e8-86b1-14ce82741e5c.png)) + +The usernames of commenters on these issues can be found in relicense_comments.txt + +There are a couple people in relicense_comments.txt who are not found in contributors.txt: + +- @EpocSquadron has [made minor text contributions to the + README](https://github.com/rust-lang/rust-clippy/commits?author=EpocSquadron) which have since been overwritten, and + doesn't count +- @JayKickliter [agreed to the relicense on their pull + request](https://github.com/rust-lang/rust-clippy/pull/3195#issuecomment-423781016) + ([archive](https://web.archive.org/web/20181005190730/https://github.com/rust-lang/rust-clippy/pull/3195), + [screenshot](https://user-images.githubusercontent.com/1617736/46573514-5cb69580-c94b-11e8-8ffb-05a5bd02e2cc.png) + +- @sanmai-NL's [contribution](https://github.com/rust-lang/rust-clippy/commits?author=sanmai-NL) is a minor one-word + addition which doesn't count for copyright assignment +- @zmt00's [contributions](https://github.com/rust-lang/rust-clippy/commits?author=zmt00) are minor typo fixes and don't + count +- @VKlayd has [nonminor contributions](https://github.com/rust-lang/rust-clippy/commits?author=VKlayd) which we rewrote + (see below) +- @wartman4404 has [nonminor contributions](https://github.com/rust-lang/rust-clippy/commits?author=wartman4404) which + we rewrote (see below) + + +Two of these contributors had nonminor contributions (#2184, #427) requiring a rewrite, carried out in #3251 +([archive](http://web.archive.org/web/20181005192411/https://github.com/rust-lang-nursery/rust-clippy/pull/3251), +[screenshot](https://user-images.githubusercontent.com/1617736/46573515-5cb69580-c94b-11e8-86e5-b456452121b2.png)) + +First, I (Manishearth) removed the lints they had added. I then documented at a high level what the lints did in #3251, +asking for co-maintainers who had not seen the code for the lints to rewrite them. #2814 was rewritten by @phansch, and +#427 was rewritten by @oli-obk, who did not recall having previously seen the code they were rewriting. + +------ + +Since this document was written, @JayKickliter and @sanmai-ML added their consent in #3230 +([archive](http://web.archive.org/web/20181006171926/https://github.com/rust-lang-nursery/rust-clippy/issues/3230)) diff --git a/src/tools/clippy/etc/relicense/contributors.txt b/src/tools/clippy/etc/relicense/contributors.txt new file mode 100644 index 0000000000000..e81ebf2148498 --- /dev/null +++ b/src/tools/clippy/etc/relicense/contributors.txt @@ -0,0 +1,232 @@ +0ndorio +0xbsec +17cupsofcoffee +Aaron1011 +Aaronepower +aaudiber +afck +alexcrichton +AlexEne +alexeyzab +alexheretic +alexreg +alusch +andersk +aochagavia +apasel422 +Arnavion +AtheMathmo +auscompgeek +AVerm +badboy +Baelyk +BenoitZugmeyer +bestouff +birkenfeld +bjgill +bkchr +Bobo1239 +bood +bootandy +b-r-u +budziq +CAD97 +Caemor +camsteffen +carols10cents +CBenoit +cesarb +cgm616 +chrisduerr +chrisvittal +chyvonomys +clarcharr +clippered +commandline +cramertj +csmoe +ctjhoa +cuviper +CYBAI +darArch +DarkEld3r +dashed +daubaris +d-dorazio +debris +dereckson +detrumi +devonhollowood +dtolnay +durka +dwijnand +eddyb +elliottneilclark +elpiel +ensch +EpicatSupercell +EpocSquadron +erickt +estk +etaoins +F001 +fanzier +FauxFaux +fhartwig +flip1995 +Fraser999 +Frederick888 +frewsxcv +gbip +gendx +gibfahn +gnieto +gnzlbg +goodmanjonathan +guido4000 +GuillaumeGomez +Hanaasagi +hdhoang +HMPerson1 +hobofan +iKevinY +illicitonion +imp +inrustwetrust +ishitatsuyuki +Jascha-N +jayhardee9 +JayKickliter +JDemler +jedisct1 +jmquigs +joelgallant +joeratt +josephDunne +JoshMcguigan +joshtriplett +jugglerchris +karyon +Keats +kennytm +Kha +killercup +kimsnj +KitFreddura +koivunej +kraai +kvikas +LaurentMazare +letheed +llogiq +lo48576 +lpesk +lucab +luisbg +lukasstevens +Machtan +MaloJaffre +Manishearth +marcusklaas +mark-i-m +martiansideofthemoon +martinlindhe +mathstuf +mati865 +matthiaskrgr +mattyhall +mbrubeck +mcarton +memoryleak47 +messense +michaelrutherford +mikerite +mipli +mockersf +montrivo +mrecachinas +Mrmaxmeier +mrmonday +ms2300 +Ms2ger +musoke +nathan +Nemo157 +NiekGr +niklasf +nrc +nweston +o01eg +ogham +oli-obk +ordovicia +pengowen123 +pgerber +phansch +philipturnbull +pickfire +pietro +PixelPirate +pizzaiter +PSeitz +Pyriphlegethon +pythonesque +quininer +Rantanen +rcoh +reiner-dolp +reujab +Robzz +samueltardieu +sanmai-NL +sanxiyn +scott-linder +scottmcm +scurest +senden9 +shahn +shepmaster +shnewto +shssoichiro +siiptuo +sinkuu +skade +sourcefrog +sourcejedi +steveklabnik +sunfishcode +sunjay +swgillespie +Techcable +terry90 +theemathas +thekidxp +theotherphil +TimNN +TomasKralCZ +tomprince +topecongiro +tspiteri +Twisol +U007D +uHOOCCOOHu +untitaker +upsuper +utaal +utam0k +vi +VKlayd +Vlad-Shcherbina +vorner +wafflespeanut +wartman4404 +waywardmonkeys +yaahallo +yangby-cryptape +yati-sagade +ykrivopalov +ysimonson +zayenz +zmanian +zmbush +zmt00 diff --git a/src/tools/clippy/etc/relicense/relicense_comments.txt b/src/tools/clippy/etc/relicense/relicense_comments.txt new file mode 100644 index 0000000000000..52c25eb201fb3 --- /dev/null +++ b/src/tools/clippy/etc/relicense/relicense_comments.txt @@ -0,0 +1,227 @@ +0ndorio +0xbsec +17cupsofcoffee +Aaron1011 +Aaronepower +aaudiber +afck +alexcrichton +AlexEne +alexeyzab +alexheretic +alexreg +alusch +andersk +aochagavia +apasel422 +Arnavion +AtheMathmo +auscompgeek +AVerm +badboy +Baelyk +BenoitZugmeyer +bestouff +birkenfeld +bjgill +bkchr +Bobo1239 +bood +bootandy +b-r-u +budziq +CAD97 +Caemor +camsteffen +carols10cents +CBenoit +cesarb +cgm616 +chrisduerr +chrisvittal +chyvonomys +clarcharr +clippered +commandline +cramertj +csmoe +ctjhoa +cuviper +CYBAI +darArch +DarkEld3r +dashed +daubaris +d-dorazio +debris +dereckson +detrumi +devonhollowood +dtolnay +durka +dwijnand +eddyb +elliottneilclark +elpiel +ensch +EpicatSupercell +erickt +estk +etaoins +F001 +fanzier +FauxFaux +fhartwig +flip1995 +Fraser999 +Frederick888 +frewsxcv +gbip +gendx +gibfahn +gnieto +gnzlbg +goodmanjonathan +guido4000 +GuillaumeGomez +Hanaasagi +hdhoang +HMPerson1 +hobofan +iKevinY +illicitonion +imp +inrustwetrust +ishitatsuyuki +Jascha-N +jayhardee9 +JDemler +jedisct1 +jmquigs +joelgallant +joeratt +josephDunne +JoshMcguigan +joshtriplett +jugglerchris +karyon +Keats +kennytm +Kha +killercup +kimsnj +KitFreddura +koivunej +kraai +kvikas +LaurentMazare +letheed +llogiq +lo48576 +lpesk +lucab +luisbg +lukasstevens +Machtan +MaloJaffre +Manishearth +marcusklaas +mark-i-m +martiansideofthemoon +martinlindhe +mathstuf +mati865 +matthiaskrgr +mattyhall +mbrubeck +mcarton +memoryleak47 +messense +michaelrutherford +mikerite +mipli +mockersf +montrivo +mrecachinas +Mrmaxmeier +mrmonday +ms2300 +Ms2ger +musoke +nathan +Nemo157 +NiekGr +niklasf +nrc +nweston +o01eg +ogham +oli-obk +ordovicia +pengowen123 +pgerber +phansch +philipturnbull +pickfire +pietro +PixelPirate +pizzaiter +PSeitz +Pyriphlegethon +pythonesque +quininer +Rantanen +rcoh +reiner-dolp +reujab +Robzz +samueltardieu +sanxiyn +scott-linder +scottmcm +scurest +senden9 +shahn +shepmaster +shnewto +shssoichiro +siiptuo +sinkuu +skade +sourcefrog +sourcejedi +steveklabnik +sunfishcode +sunjay +swgillespie +Techcable +terry90 +theemathas +thekidxp +theotherphil +TimNN +TomasKralCZ +tommilligan +tomprince +topecongiro +tspiteri +Twisol +U007D +uHOOCCOOHu +untitaker +upsuper +utaal +utam0k +vi +Vlad-Shcherbina +vorner +wafflespeanut +waywardmonkeys +yaahallo +yangby-cryptape +yati-sagade +ykrivopalov +ysimonson +zayenz +zmanian +zmbush diff --git a/src/tools/clippy/mini-macro/Cargo.toml b/src/tools/clippy/mini-macro/Cargo.toml new file mode 100644 index 0000000000000..75ab17588a7f4 --- /dev/null +++ b/src/tools/clippy/mini-macro/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "clippy-mini-macro-test" +version = "0.2.0" +authors = [ + "Manish Goregaokar ", + "Andre Bogus ", + "Georg Brandl ", + "Martin Carton ", + "Oliver Schneider " +] +license = "MIT OR Apache-2.0" +description = "A macro to test clippy's procedural macro checks" +repository = "https://github.com/rust-lang/rust-clippy" +edition = "2018" + +[lib] +name = "clippy_mini_macro_test" +proc-macro = true + +[dependencies] diff --git a/src/tools/clippy/mini-macro/src/lib.rs b/src/tools/clippy/mini-macro/src/lib.rs new file mode 100644 index 0000000000000..92b6f70155577 --- /dev/null +++ b/src/tools/clippy/mini-macro/src/lib.rs @@ -0,0 +1,26 @@ +#![feature(proc_macro_quote, proc_macro_hygiene)] +#![deny(rust_2018_idioms)] +// FIXME: Remove this attribute once the weird failure is gone. +#![allow(unused_extern_crates)] +extern crate proc_macro; + +use proc_macro::{quote, TokenStream}; + +#[proc_macro_derive(ClippyMiniMacroTest)] +pub fn mini_macro(_: TokenStream) -> TokenStream { + quote!( + #[allow(unused)] + fn needless_take_by_value(s: String) { + println!("{}", s.len()); + } + #[allow(unused)] + fn needless_loop(items: &[u8]) { + for i in 0..items.len() { + println!("{}", items[i]); + } + } + fn line_wrapper() { + println!("{}", line!()); + } + ) +} diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain new file mode 100644 index 0000000000000..bf867e0ae5b6c --- /dev/null +++ b/src/tools/clippy/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/src/tools/clippy/rustc_tools_util/Cargo.toml b/src/tools/clippy/rustc_tools_util/Cargo.toml new file mode 100644 index 0000000000000..6f0fc5bee8f09 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rustc_tools_util" +version = "0.2.0" +authors = ["Matthias Krüger "] +description = "small helper to generate version information for git packages" +repository = "https://github.com/rust-lang/rust-clippy" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["rustc", "tool", "git", "version", "hash"] +categories = ["development-tools"] +edition = "2018" + +[dependencies] + +[features] +deny-warnings = [] diff --git a/src/tools/clippy/rustc_tools_util/README.md b/src/tools/clippy/rustc_tools_util/README.md new file mode 100644 index 0000000000000..6027538dc4ab2 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/README.md @@ -0,0 +1,62 @@ +# rustc_tools_util + +A small tool to help you generate version information +for packages installed from a git repo + +## Usage + +Add a `build.rs` file to your repo and list it in `Cargo.toml` +```` +build = "build.rs" +```` + +List rustc_tools_util as regular AND build dependency. +```` +[dependencies] +rustc_tools_util = "0.1" + +[build-dependencies] +rustc_tools_util = "0.1" +```` + +In `build.rs`, generate the data in your `main()` +````rust +fn main() { + println!( + "cargo:rustc-env=GIT_HASH={}", + rustc_tools_util::get_commit_hash().unwrap_or_default() + ); + println!( + "cargo:rustc-env=COMMIT_DATE={}", + rustc_tools_util::get_commit_date().unwrap_or_default() + ); + println!( + "cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}", + rustc_tools_util::get_channel().unwrap_or_default() + ); +} + +```` + +Use the version information in your main.rs +````rust +use rustc_tools_util::*; + +fn show_version() { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); +} +```` +This gives the following output in clippy: +`clippy 0.0.212 (a416c5e 2018-12-14)` + + +## License + +Copyright 2014-2020 The Rust Project Developers + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. All files in the project carrying such notice may not be +copied, modified, or distributed except according to those terms. diff --git a/src/tools/clippy/rustc_tools_util/src/lib.rs b/src/tools/clippy/rustc_tools_util/src/lib.rs new file mode 100644 index 0000000000000..ff2a7de572571 --- /dev/null +++ b/src/tools/clippy/rustc_tools_util/src/lib.rs @@ -0,0 +1,162 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] + +use std::env; + +#[macro_export] +macro_rules! get_version_info { + () => {{ + let major = env!("CARGO_PKG_VERSION_MAJOR").parse::().unwrap(); + let minor = env!("CARGO_PKG_VERSION_MINOR").parse::().unwrap(); + let patch = env!("CARGO_PKG_VERSION_PATCH").parse::().unwrap(); + let crate_name = String::from(env!("CARGO_PKG_NAME")); + + let host_compiler = option_env!("RUSTC_RELEASE_CHANNEL").map(str::to_string); + let commit_hash = option_env!("GIT_HASH").map(str::to_string); + let commit_date = option_env!("COMMIT_DATE").map(str::to_string); + + VersionInfo { + major, + minor, + patch, + host_compiler, + commit_hash, + commit_date, + crate_name, + } + }}; +} + +// some code taken and adapted from RLS and cargo +pub struct VersionInfo { + pub major: u8, + pub minor: u8, + pub patch: u16, + pub host_compiler: Option, + pub commit_hash: Option, + pub commit_date: Option, + pub crate_name: String, +} + +impl std::fmt::Display for VersionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hash = self.commit_hash.clone().unwrap_or_default(); + let hash_trimmed = hash.trim(); + + let date = self.commit_date.clone().unwrap_or_default(); + let date_trimmed = date.trim(); + + if (hash_trimmed.len() + date_trimmed.len()) > 0 { + write!( + f, + "{} {}.{}.{} ({} {})", + self.crate_name, self.major, self.minor, self.patch, hash_trimmed, date_trimmed, + )?; + } else { + write!(f, "{} {}.{}.{}", self.crate_name, self.major, self.minor, self.patch)?; + } + + Ok(()) + } +} + +impl std::fmt::Debug for VersionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "VersionInfo {{ crate_name: \"{}\", major: {}, minor: {}, patch: {}", + self.crate_name, self.major, self.minor, self.patch, + )?; + if self.commit_hash.is_some() { + write!( + f, + ", commit_hash: \"{}\", commit_date: \"{}\" }}", + self.commit_hash.clone().unwrap_or_default().trim(), + self.commit_date.clone().unwrap_or_default().trim() + )?; + } else { + write!(f, " }}")?; + } + + Ok(()) + } +} + +#[must_use] +pub fn get_commit_hash() -> Option { + std::process::Command::new("git") + .args(&["rev-parse", "--short", "HEAD"]) + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) +} + +#[must_use] +pub fn get_commit_date() -> Option { + std::process::Command::new("git") + .args(&["log", "-1", "--date=short", "--pretty=format:%cd"]) + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) +} + +#[must_use] +pub fn get_channel() -> Option { + match env::var("CFG_RELEASE_CHANNEL") { + Ok(channel) => Some(channel), + Err(_) => { + // if that failed, try to ask rustc -V, do some parsing and find out + match std::process::Command::new("rustc") + .arg("-V") + .output() + .ok() + .and_then(|r| String::from_utf8(r.stdout).ok()) + { + Some(rustc_output) => { + if rustc_output.contains("beta") { + Some(String::from("beta")) + } else if rustc_output.contains("stable") { + Some(String::from("stable")) + } else { + // default to nightly if we fail to parse + Some(String::from("nightly")) + } + }, + // default to nightly + None => Some(String::from("nightly")), + } + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_struct_local() { + let vi = get_version_info!(); + assert_eq!(vi.major, 0); + assert_eq!(vi.minor, 2); + assert_eq!(vi.patch, 0); + assert_eq!(vi.crate_name, "rustc_tools_util"); + // hard to make positive tests for these since they will always change + assert!(vi.commit_hash.is_none()); + assert!(vi.commit_date.is_none()); + } + + #[test] + fn test_display_local() { + let vi = get_version_info!(); + assert_eq!(vi.to_string(), "rustc_tools_util 0.2.0"); + } + + #[test] + fn test_debug_local() { + let vi = get_version_info!(); + let s = format!("{:?}", vi); + assert_eq!( + s, + "VersionInfo { crate_name: \"rustc_tools_util\", major: 0, minor: 2, patch: 0 }" + ); + } +} diff --git a/src/tools/clippy/rustfmt.toml b/src/tools/clippy/rustfmt.toml new file mode 100644 index 0000000000000..f1241e74b0a3d --- /dev/null +++ b/src/tools/clippy/rustfmt.toml @@ -0,0 +1,6 @@ +max_width = 120 +comment_width = 100 +match_block_trailing_comma = true +wrap_comments = true +edition = "2018" +error_on_line_overflow = true diff --git a/src/tools/clippy/setup-toolchain.sh b/src/tools/clippy/setup-toolchain.sh new file mode 100755 index 0000000000000..6038ed697f91e --- /dev/null +++ b/src/tools/clippy/setup-toolchain.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Set up the appropriate rustc toolchain + +set -e + +cd "$(dirname "$0")" + +RTIM_PATH=$(command -v rustup-toolchain-install-master) || INSTALLED=false +CARGO_HOME=${CARGO_HOME:-$HOME/.cargo} + +# Check if RTIM is not installed or installed in other locations not in ~/.cargo/bin +if [[ "$INSTALLED" == false || "$RTIM_PATH" == $CARGO_HOME/bin/rustup-toolchain-install-master ]]; then + cargo +nightly install rustup-toolchain-install-master +else + VERSION=$(rustup-toolchain-install-master -V | grep -o "[0-9.]*") + REMOTE=$(cargo +nightly search rustup-toolchain-install-master | grep -o "[0-9.]*") + echo "info: skipping updating rustup-toolchain-install-master at $RTIM_PATH" + echo " current version : $VERSION" + echo " remote version : $REMOTE" +fi + +RUST_COMMIT=$(git ls-remote https://github.com/rust-lang/rust master | awk '{print $1}') + +if rustc +master -Vv 2>/dev/null | grep -q "$RUST_COMMIT"; then + echo "info: master toolchain is up-to-date" + exit 0 +fi + +if [[ -n "$HOST_TOOLCHAIN" ]]; then + TOOLCHAIN=('--host' "$HOST_TOOLCHAIN") +else + TOOLCHAIN=() +fi + +rustup-toolchain-install-master -f -n master "${TOOLCHAIN[@]}" -c rustc-dev -- "$RUST_COMMIT" +rustup override set master diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs new file mode 100644 index 0000000000000..2c699998ea90e --- /dev/null +++ b/src/tools/clippy/src/driver.rs @@ -0,0 +1,417 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![feature(rustc_private)] +#![feature(str_strip)] + +// FIXME: switch to something more ergonomic here, once available. +// (Currently there is no way to opt into sysroot crates without `extern crate`.) +#[allow(unused_extern_crates)] +extern crate rustc_driver; +#[allow(unused_extern_crates)] +extern crate rustc_errors; +#[allow(unused_extern_crates)] +extern crate rustc_interface; +#[allow(unused_extern_crates)] +extern crate rustc_middle; + +use rustc_interface::interface; +use rustc_middle::ty::TyCtxt; +use rustc_tools_util::VersionInfo; + +use lazy_static::lazy_static; +use std::borrow::Cow; +use std::env; +use std::ops::Deref; +use std::panic; +use std::path::{Path, PathBuf}; +use std::process::{exit, Command}; + +mod lintlist; + +/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If +/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`. +fn arg_value<'a, T: Deref>( + args: &'a [T], + find_arg: &str, + pred: impl Fn(&str) -> bool, +) -> Option<&'a str> { + let mut args = args.iter().map(Deref::deref); + while let Some(arg) = args.next() { + let mut arg = arg.splitn(2, '='); + if arg.next() != Some(find_arg) { + continue; + } + + match arg.next().or_else(|| args.next()) { + Some(v) if pred(v) => return Some(v), + _ => {}, + } + } + None +} + +#[test] +fn test_arg_value() { + let args = &["--bar=bar", "--foobar", "123", "--foo"]; + + assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None); + assert_eq!(arg_value(args, "--bar", |_| false), None); + assert_eq!(arg_value(args, "--bar", |_| true), Some("bar")); + assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar")); + assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None); + assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None); + assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123")); + assert_eq!(arg_value(args, "--foo", |_| true), None); +} + +struct DefaultCallbacks; +impl rustc_driver::Callbacks for DefaultCallbacks {} + +struct ClippyCallbacks; +impl rustc_driver::Callbacks for ClippyCallbacks { + fn config(&mut self, config: &mut interface::Config) { + let previous = config.register_lints.take(); + config.register_lints = Some(Box::new(move |sess, mut lint_store| { + // technically we're ~guaranteed that this is none but might as well call anything that + // is there already. Certainly it can't hurt. + if let Some(previous) = &previous { + (previous)(sess, lint_store); + } + + let conf = clippy_lints::read_conf(&[], &sess); + clippy_lints::register_plugins(&mut lint_store, &sess, &conf); + clippy_lints::register_pre_expansion_lints(&mut lint_store, &conf); + clippy_lints::register_renamed(&mut lint_store); + })); + + // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be + // run on the unoptimized MIR. On the other hand this results in some false negatives. If + // MIR passes can be enabled / disabled separately, we should figure out, what passes to + // use for Clippy. + config.opts.debugging_opts.mir_opt_level = 0; + } +} + +#[allow(clippy::find_map, clippy::filter_map)] +fn describe_lints() { + use lintlist::{Level, Lint, ALL_LINTS, LINT_LEVELS}; + use std::collections::HashSet; + + println!( + " +Available lint options: + -W Warn about + -A Allow + -D Deny + -F Forbid (deny and all attempts to override) + +" + ); + + let lint_level = |lint: &Lint| { + LINT_LEVELS + .iter() + .find(|level_mapping| level_mapping.0 == lint.group) + .map(|(_, level)| match level { + Level::Allow => "allow", + Level::Warn => "warn", + Level::Deny => "deny", + }) + .unwrap() + }; + + let mut lints: Vec<_> = ALL_LINTS.iter().collect(); + // The sort doesn't case-fold but it's doubtful we care. + lints.sort_by_cached_key(|x: &&Lint| (lint_level(x), x.name)); + + let max_lint_name_len = lints + .iter() + .map(|lint| lint.name.len()) + .map(|len| len + "clippy::".len()) + .max() + .unwrap_or(0); + + let padded = |x: &str| { + let mut s = " ".repeat(max_lint_name_len - x.chars().count()); + s.push_str(x); + s + }; + + let scoped = |x: &str| format!("clippy::{}", x); + + let lint_groups: HashSet<_> = lints.iter().map(|lint| lint.group).collect(); + + println!("Lint checks provided by clippy:\n"); + println!(" {} {:7.7} meaning", padded("name"), "default"); + println!(" {} {:7.7} -------", padded("----"), "-------"); + + let print_lints = |lints: &[&Lint]| { + for lint in lints { + let name = lint.name.replace("_", "-"); + println!( + " {} {:7.7} {}", + padded(&scoped(&name)), + lint_level(lint), + lint.desc + ); + } + println!("\n"); + }; + + print_lints(&lints); + + let max_group_name_len = std::cmp::max( + "clippy::all".len(), + lint_groups + .iter() + .map(|group| group.len()) + .map(|len| len + "clippy::".len()) + .max() + .unwrap_or(0), + ); + + let padded_group = |x: &str| { + let mut s = " ".repeat(max_group_name_len - x.chars().count()); + s.push_str(x); + s + }; + + println!("Lint groups provided by clippy:\n"); + println!(" {} sub-lints", padded_group("name")); + println!(" {} ---------", padded_group("----")); + println!(" {} the set of all clippy lints", padded_group("clippy::all")); + + let print_lint_groups = || { + for group in lint_groups { + let name = group.to_lowercase().replace("_", "-"); + let desc = lints + .iter() + .filter(|&lint| lint.group == group) + .map(|lint| lint.name) + .map(|name| name.replace("_", "-")) + .collect::>() + .join(", "); + println!(" {} {}", padded_group(&scoped(&name)), desc); + } + println!("\n"); + }; + + print_lint_groups(); +} + +fn display_help() { + println!( + "\ +Checks a package to catch common mistakes and improve your Rust code. + +Usage: + cargo clippy [options] [--] [...] + +Common options: + -h, --help Print this message + -V, --version Print version info and exit + +Other options are the same as `cargo check`. + +To allow or deny a lint from the command line you can use `cargo clippy --` +with: + + -W --warn OPT Set lint warnings + -A --allow OPT Set lint allowed + -D --deny OPT Set lint denied + -F --forbid OPT Set lint forbidden + +You can use tool lints to allow or deny lints from your code, eg.: + + #[allow(clippy::needless_lifetimes)] +" + ); +} + +const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; + +lazy_static! { + static ref ICE_HOOK: Box) + Sync + Send + 'static> = { + let hook = panic::take_hook(); + panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL))); + hook + }; +} + +fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { + // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace + (*ICE_HOOK)(info); + + // Separate the output with an empty line + eprintln!(); + + let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( + rustc_errors::ColorConfig::Auto, + None, + false, + false, + None, + false, + )); + let handler = rustc_errors::Handler::with_emitter(true, None, emitter); + + // a .span_bug or .bug call has already printed what + // it wants to print. + if !info.payload().is::() { + let d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic"); + handler.emit_diagnostic(&d); + } + + let version_info = rustc_tools_util::get_version_info!(); + + let xs: Vec> = vec![ + "the compiler unexpectedly panicked. this is a bug.".into(), + format!("we would appreciate a bug report: {}", bug_report_url).into(), + format!("Clippy version: {}", version_info).into(), + ]; + + for note in &xs { + handler.note_without_error(¬e); + } + + // If backtraces are enabled, also print the query stack + let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0"); + + if backtrace { + TyCtxt::try_print_query_stack(&handler); + } +} + +fn toolchain_path(home: Option, toolchain: Option) -> Option { + home.and_then(|home| { + toolchain.map(|toolchain| { + let mut path = PathBuf::from(home); + path.push("toolchains"); + path.push(toolchain); + path + }) + }) +} + +pub fn main() { + rustc_driver::init_rustc_env_logger(); + lazy_static::initialize(&ICE_HOOK); + exit( + rustc_driver::catch_fatal_errors(move || { + let mut orig_args: Vec = env::args().collect(); + + if orig_args.iter().any(|a| a == "--version" || a == "-V") { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); + exit(0); + } + + // Get the sysroot, looking from most specific to this invocation to the least: + // - command line + // - runtime environment + // - SYSROOT + // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN + // - sysroot from rustc in the path + // - compile-time environment + // - SYSROOT + // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN + let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true); + let have_sys_root_arg = sys_root_arg.is_some(); + let sys_root = sys_root_arg + .map(PathBuf::from) + .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from)) + .or_else(|| { + let home = std::env::var("RUSTUP_HOME") + .or_else(|_| std::env::var("MULTIRUST_HOME")) + .ok(); + let toolchain = std::env::var("RUSTUP_TOOLCHAIN") + .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN")) + .ok(); + toolchain_path(home, toolchain) + }) + .or_else(|| { + Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output() + .ok() + .and_then(|out| String::from_utf8(out.stdout).ok()) + .map(|s| PathBuf::from(s.trim())) + }) + .or_else(|| option_env!("SYSROOT").map(PathBuf::from)) + .or_else(|| { + let home = option_env!("RUSTUP_HOME") + .or(option_env!("MULTIRUST_HOME")) + .map(ToString::to_string); + let toolchain = option_env!("RUSTUP_TOOLCHAIN") + .or(option_env!("MULTIRUST_TOOLCHAIN")) + .map(ToString::to_string); + toolchain_path(home, toolchain) + }) + .map(|pb| pb.to_string_lossy().to_string()) + .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust"); + + // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. + // We're invoking the compiler programmatically, so we ignore this/ + let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref()); + + if wrapper_mode { + // we still want to be able to invoke it normally though + orig_args.remove(1); + } + + if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) { + display_help(); + exit(0); + } + + let should_describe_lints = || { + let args: Vec<_> = env::args().collect(); + args.windows(2).any(|args| { + args[1] == "help" + && match args[0].as_str() { + "-W" | "-A" | "-D" | "-F" => true, + _ => false, + } + }) + }; + + if !wrapper_mode && should_describe_lints() { + describe_lints(); + exit(0); + } + + // this conditional check for the --sysroot flag is there so users can call + // `clippy_driver` directly + // without having to pass --sysroot or anything + let mut args: Vec = orig_args.clone(); + if !have_sys_root_arg { + args.extend(vec!["--sysroot".into(), sys_root]); + }; + + // this check ensures that dependencies are built but not linted and the final + // crate is linted but not built + let clippy_enabled = env::var("CLIPPY_TESTS").map_or(false, |val| val == "true") + || arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_none(); + + if clippy_enabled { + args.extend(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]); + if let Ok(extra_args) = env::var("CLIPPY_ARGS") { + args.extend(extra_args.split("__CLIPPY_HACKERY__").filter_map(|s| { + if s.is_empty() { + None + } else { + Some(s.to_string()) + } + })); + } + } + let mut clippy = ClippyCallbacks; + let mut default = DefaultCallbacks; + let callbacks: &mut (dyn rustc_driver::Callbacks + Send) = + if clippy_enabled { &mut clippy } else { &mut default }; + rustc_driver::run_compiler(&args, callbacks, None, None) + }) + .and_then(|result| result) + .is_err() as i32, + ) +} diff --git a/src/tools/clippy/src/lintlist/lint.rs b/src/tools/clippy/src/lintlist/lint.rs new file mode 100644 index 0000000000000..c817d83b33aeb --- /dev/null +++ b/src/tools/clippy/src/lintlist/lint.rs @@ -0,0 +1,27 @@ +/// Lint data parsed from the Clippy source code. +#[derive(Clone, PartialEq, Debug)] +pub struct Lint { + pub name: &'static str, + pub group: &'static str, + pub desc: &'static str, + pub deprecation: Option<&'static str>, + pub module: &'static str, +} + +#[derive(PartialOrd, PartialEq, Ord, Eq)] +pub enum Level { + Allow, + Warn, + Deny, +} + +pub const LINT_LEVELS: [(&str, Level); 8] = [ + ("correctness", Level::Deny), + ("style", Level::Warn), + ("complexity", Level::Warn), + ("perf", Level::Warn), + ("restriction", Level::Allow), + ("pedantic", Level::Allow), + ("nursery", Level::Allow), + ("cargo", Level::Allow), +]; diff --git a/src/tools/clippy/src/lintlist/mod.rs b/src/tools/clippy/src/lintlist/mod.rs new file mode 100644 index 0000000000000..72675c25175c9 --- /dev/null +++ b/src/tools/clippy/src/lintlist/mod.rs @@ -0,0 +1,2633 @@ +//! This file is managed by `cargo dev update_lints`. Do not edit. + +use lazy_static::lazy_static; + +pub mod lint; +pub use lint::Level; +pub use lint::Lint; +pub use lint::LINT_LEVELS; + +lazy_static! { +// begin lint list, do not remove this comment, it’s used in `update_lints` +pub static ref ALL_LINTS: Vec = vec![ + Lint { + name: "absurd_extreme_comparisons", + group: "correctness", + desc: "a comparison with a maximum or minimum value that is always true or false", + deprecation: None, + module: "types", + }, + Lint { + name: "almost_swapped", + group: "correctness", + desc: "`foo = bar; bar = foo` sequence", + deprecation: None, + module: "swap", + }, + Lint { + name: "approx_constant", + group: "correctness", + desc: "the approximate of a known float constant (in `std::fXX::consts`)", + deprecation: None, + module: "approx_const", + }, + Lint { + name: "as_conversions", + group: "restriction", + desc: "using a potentially dangerous silent `as` conversion", + deprecation: None, + module: "as_conversions", + }, + Lint { + name: "assertions_on_constants", + group: "style", + desc: "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`", + deprecation: None, + module: "assertions_on_constants", + }, + Lint { + name: "assign_op_pattern", + group: "style", + desc: "assigning the result of an operation on a variable to that same variable", + deprecation: None, + module: "assign_ops", + }, + Lint { + name: "await_holding_lock", + group: "pedantic", + desc: "Inside an async function, holding a MutexGuard while calling await", + deprecation: None, + module: "await_holding_lock", + }, + Lint { + name: "bad_bit_mask", + group: "correctness", + desc: "expressions of the form `_ & mask == select` that will only ever return `true` or `false`", + deprecation: None, + module: "bit_mask", + }, + Lint { + name: "blacklisted_name", + group: "style", + desc: "usage of a blacklisted/placeholder name", + deprecation: None, + module: "blacklisted_name", + }, + Lint { + name: "block_in_if_condition_expr", + group: "style", + desc: "braces that can be eliminated in conditions, e.g., `if { true } ...`", + deprecation: None, + module: "block_in_if_condition", + }, + Lint { + name: "block_in_if_condition_stmt", + group: "style", + desc: "complex blocks in conditions, e.g., `if { let x = true; x } ...`", + deprecation: None, + module: "block_in_if_condition", + }, + Lint { + name: "bool_comparison", + group: "complexity", + desc: "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`", + deprecation: None, + module: "needless_bool", + }, + Lint { + name: "borrow_interior_mutable_const", + group: "correctness", + desc: "referencing `const` with interior mutability", + deprecation: None, + module: "non_copy_const", + }, + Lint { + name: "borrowed_box", + group: "complexity", + desc: "a borrow of a boxed type", + deprecation: None, + module: "types", + }, + Lint { + name: "box_vec", + group: "perf", + desc: "usage of `Box>`, vector elements are already on the heap", + deprecation: None, + module: "types", + }, + Lint { + name: "boxed_local", + group: "perf", + desc: "using `Box` where unnecessary", + deprecation: None, + module: "escape", + }, + Lint { + name: "builtin_type_shadow", + group: "style", + desc: "shadowing a builtin type", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "cargo_common_metadata", + group: "cargo", + desc: "common metadata is defined in `Cargo.toml`", + deprecation: None, + module: "cargo_common_metadata", + }, + Lint { + name: "cast_lossless", + group: "pedantic", + desc: "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`", + deprecation: None, + module: "types", + }, + Lint { + name: "cast_possible_truncation", + group: "pedantic", + desc: "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`", + deprecation: None, + module: "types", + }, + Lint { + name: "cast_possible_wrap", + group: "pedantic", + desc: "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`", + deprecation: None, + module: "types", + }, + Lint { + name: "cast_precision_loss", + group: "pedantic", + desc: "casts that cause loss of precision, e.g., `x as f32` where `x: u64`", + deprecation: None, + module: "types", + }, + Lint { + name: "cast_ptr_alignment", + group: "correctness", + desc: "cast from a pointer to a more-strictly-aligned pointer", + deprecation: None, + module: "types", + }, + Lint { + name: "cast_ref_to_mut", + group: "correctness", + desc: "a cast of reference to a mutable pointer", + deprecation: None, + module: "types", + }, + Lint { + name: "cast_sign_loss", + group: "pedantic", + desc: "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`", + deprecation: None, + module: "types", + }, + Lint { + name: "char_lit_as_u8", + group: "complexity", + desc: "casting a character literal to `u8` truncates", + deprecation: None, + module: "types", + }, + Lint { + name: "chars_last_cmp", + group: "style", + desc: "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char", + deprecation: None, + module: "methods", + }, + Lint { + name: "chars_next_cmp", + group: "style", + desc: "using `.chars().next()` to check if a string starts with a char", + deprecation: None, + module: "methods", + }, + Lint { + name: "checked_conversions", + group: "pedantic", + desc: "`try_from` could replace manual bounds checking when casting", + deprecation: None, + module: "checked_conversions", + }, + Lint { + name: "clone_double_ref", + group: "correctness", + desc: "using `clone` on `&&T`", + deprecation: None, + module: "methods", + }, + Lint { + name: "clone_on_copy", + group: "complexity", + desc: "using `clone` on a `Copy` type", + deprecation: None, + module: "methods", + }, + Lint { + name: "clone_on_ref_ptr", + group: "restriction", + desc: "using \'clone\' on a ref-counted pointer", + deprecation: None, + module: "methods", + }, + Lint { + name: "cmp_nan", + group: "correctness", + desc: "comparisons to `NAN`, which will always return false, probably not intended", + deprecation: None, + module: "misc", + }, + Lint { + name: "cmp_null", + group: "style", + desc: "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead.", + deprecation: None, + module: "ptr", + }, + Lint { + name: "cmp_owned", + group: "perf", + desc: "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`", + deprecation: None, + module: "misc", + }, + Lint { + name: "cognitive_complexity", + group: "nursery", + desc: "functions that should be split up into multiple functions", + deprecation: None, + module: "cognitive_complexity", + }, + Lint { + name: "collapsible_if", + group: "style", + desc: "`if`s that can be collapsed (e.g., `if x { if y { ... } }` and `else { if x { ... } }`)", + deprecation: None, + module: "collapsible_if", + }, + Lint { + name: "comparison_chain", + group: "style", + desc: "`if`s that can be rewritten with `match` and `cmp`", + deprecation: None, + module: "comparison_chain", + }, + Lint { + name: "copy_iterator", + group: "pedantic", + desc: "implementing `Iterator` on a `Copy` type", + deprecation: None, + module: "copy_iterator", + }, + Lint { + name: "crosspointer_transmute", + group: "complexity", + desc: "transmutes that have to or from types that are a pointer to the other", + deprecation: None, + module: "transmute", + }, + Lint { + name: "dbg_macro", + group: "restriction", + desc: "`dbg!` macro is intended as a debugging tool", + deprecation: None, + module: "dbg_macro", + }, + Lint { + name: "debug_assert_with_mut_call", + group: "nursery", + desc: "mutable arguments in `debug_assert{,_ne,_eq}!`", + deprecation: None, + module: "mutable_debug_assertion", + }, + Lint { + name: "decimal_literal_representation", + group: "restriction", + desc: "using decimal representation when hexadecimal would be better", + deprecation: None, + module: "literal_representation", + }, + Lint { + name: "declare_interior_mutable_const", + group: "correctness", + desc: "declaring `const` with interior mutability", + deprecation: None, + module: "non_copy_const", + }, + Lint { + name: "default_trait_access", + group: "pedantic", + desc: "checks for literal calls to `Default::default()`", + deprecation: None, + module: "default_trait_access", + }, + Lint { + name: "deprecated_cfg_attr", + group: "complexity", + desc: "usage of `cfg_attr(rustfmt)` instead of tool attributes", + deprecation: None, + module: "attrs", + }, + Lint { + name: "deprecated_semver", + group: "correctness", + desc: "use of `#[deprecated(since = \"x\")]` where x is not semver", + deprecation: None, + module: "attrs", + }, + Lint { + name: "deref_addrof", + group: "complexity", + desc: "use of `*&` or `*&mut` in an expression", + deprecation: None, + module: "reference", + }, + Lint { + name: "derive_hash_xor_eq", + group: "correctness", + desc: "deriving `Hash` but implementing `PartialEq` explicitly", + deprecation: None, + module: "derive", + }, + Lint { + name: "diverging_sub_expression", + group: "complexity", + desc: "whether an expression contains a diverging sub expression", + deprecation: None, + module: "eval_order_dependence", + }, + Lint { + name: "doc_markdown", + group: "pedantic", + desc: "presence of `_`, `::` or camel-case outside backticks in documentation", + deprecation: None, + module: "doc", + }, + Lint { + name: "double_comparisons", + group: "complexity", + desc: "unnecessary double comparisons that can be simplified", + deprecation: None, + module: "double_comparison", + }, + Lint { + name: "double_must_use", + group: "style", + desc: "`#[must_use]` attribute on a `#[must_use]`-returning function / method", + deprecation: None, + module: "functions", + }, + Lint { + name: "double_neg", + group: "style", + desc: "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "double_parens", + group: "complexity", + desc: "Warn on unnecessary double parentheses", + deprecation: None, + module: "double_parens", + }, + Lint { + name: "drop_bounds", + group: "correctness", + desc: "Bounds of the form `T: Drop` are useless", + deprecation: None, + module: "drop_bounds", + }, + Lint { + name: "drop_copy", + group: "correctness", + desc: "calls to `std::mem::drop` with a value that implements Copy", + deprecation: None, + module: "drop_forget_ref", + }, + Lint { + name: "drop_ref", + group: "correctness", + desc: "calls to `std::mem::drop` with a reference instead of an owned value", + deprecation: None, + module: "drop_forget_ref", + }, + Lint { + name: "duplicate_underscore_argument", + group: "style", + desc: "function arguments having names which only differ by an underscore", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "duration_subsec", + group: "complexity", + desc: "checks for calculation of subsecond microseconds or milliseconds", + deprecation: None, + module: "duration_subsec", + }, + Lint { + name: "else_if_without_else", + group: "restriction", + desc: "`if` expression with an `else if`, but without a final `else` branch", + deprecation: None, + module: "else_if_without_else", + }, + Lint { + name: "empty_enum", + group: "pedantic", + desc: "enum with no variants", + deprecation: None, + module: "empty_enum", + }, + Lint { + name: "empty_line_after_outer_attr", + group: "nursery", + desc: "empty line after outer attribute", + deprecation: None, + module: "attrs", + }, + Lint { + name: "empty_loop", + group: "style", + desc: "empty `loop {}`, which should block or sleep", + deprecation: None, + module: "loops", + }, + Lint { + name: "enum_clike_unportable_variant", + group: "correctness", + desc: "C-like enums that are `repr(isize/usize)` and have values that don\'t fit into an `i32`", + deprecation: None, + module: "enum_clike", + }, + Lint { + name: "enum_glob_use", + group: "pedantic", + desc: "use items that import all variants of an enum", + deprecation: None, + module: "wildcard_imports", + }, + Lint { + name: "enum_variant_names", + group: "style", + desc: "enums where all variants share a prefix/postfix", + deprecation: None, + module: "enum_variants", + }, + Lint { + name: "eq_op", + group: "correctness", + desc: "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)", + deprecation: None, + module: "eq_op", + }, + Lint { + name: "erasing_op", + group: "correctness", + desc: "using erasing operations, e.g., `x * 0` or `y & 0`", + deprecation: None, + module: "erasing_op", + }, + Lint { + name: "eval_order_dependence", + group: "complexity", + desc: "whether a variable read occurs before a write depends on sub-expression evaluation order", + deprecation: None, + module: "eval_order_dependence", + }, + Lint { + name: "excessive_precision", + group: "style", + desc: "excessive precision for float literal", + deprecation: None, + module: "float_literal", + }, + Lint { + name: "exit", + group: "restriction", + desc: "`std::process::exit` is called, terminating the program", + deprecation: None, + module: "exit", + }, + Lint { + name: "expect_fun_call", + group: "perf", + desc: "using any `expect` method with a function call", + deprecation: None, + module: "methods", + }, + Lint { + name: "expl_impl_clone_on_copy", + group: "pedantic", + desc: "implementing `Clone` explicitly on `Copy` types", + deprecation: None, + module: "derive", + }, + Lint { + name: "explicit_counter_loop", + group: "complexity", + desc: "for-looping with an explicit counter when `_.enumerate()` would do", + deprecation: None, + module: "loops", + }, + Lint { + name: "explicit_deref_methods", + group: "pedantic", + desc: "Explicit use of deref or deref_mut method while not in a method chain.", + deprecation: None, + module: "dereference", + }, + Lint { + name: "explicit_into_iter_loop", + group: "pedantic", + desc: "for-looping over `_.into_iter()` when `_` would do", + deprecation: None, + module: "loops", + }, + Lint { + name: "explicit_iter_loop", + group: "pedantic", + desc: "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do", + deprecation: None, + module: "loops", + }, + Lint { + name: "explicit_write", + group: "complexity", + desc: "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work", + deprecation: None, + module: "explicit_write", + }, + Lint { + name: "extra_unused_lifetimes", + group: "complexity", + desc: "unused lifetimes in function definitions", + deprecation: None, + module: "lifetimes", + }, + Lint { + name: "fallible_impl_from", + group: "nursery", + desc: "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`", + deprecation: None, + module: "fallible_impl_from", + }, + Lint { + name: "filetype_is_file", + group: "restriction", + desc: "`FileType::is_file` is not recommended to test for readable file type", + deprecation: None, + module: "methods", + }, + Lint { + name: "filter_map", + group: "pedantic", + desc: "using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call", + deprecation: None, + module: "methods", + }, + Lint { + name: "filter_map_next", + group: "pedantic", + desc: "using combination of `filter_map` and `next` which can usually be written as a single method call", + deprecation: None, + module: "methods", + }, + Lint { + name: "filter_next", + group: "complexity", + desc: "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "find_map", + group: "pedantic", + desc: "using a combination of `find` and `map` can usually be written as a single method call", + deprecation: None, + module: "methods", + }, + Lint { + name: "flat_map_identity", + group: "complexity", + desc: "call to `flat_map` where `flatten` is sufficient", + deprecation: None, + module: "methods", + }, + Lint { + name: "float_arithmetic", + group: "restriction", + desc: "any floating-point arithmetic statement", + deprecation: None, + module: "arithmetic", + }, + Lint { + name: "float_cmp", + group: "correctness", + desc: "using `==` or `!=` on float values instead of comparing difference with an epsilon", + deprecation: None, + module: "misc", + }, + Lint { + name: "float_cmp_const", + group: "restriction", + desc: "using `==` or `!=` on float constants instead of comparing difference with an epsilon", + deprecation: None, + module: "misc", + }, + Lint { + name: "fn_address_comparisons", + group: "correctness", + desc: "comparison with an address of a function item", + deprecation: None, + module: "unnamed_address", + }, + Lint { + name: "fn_params_excessive_bools", + group: "pedantic", + desc: "using too many bools in function parameters", + deprecation: None, + module: "excessive_bools", + }, + Lint { + name: "fn_to_numeric_cast", + group: "style", + desc: "casting a function pointer to a numeric type other than usize", + deprecation: None, + module: "types", + }, + Lint { + name: "fn_to_numeric_cast_with_truncation", + group: "style", + desc: "casting a function pointer to a numeric type not wide enough to store the address", + deprecation: None, + module: "types", + }, + Lint { + name: "for_kv_map", + group: "style", + desc: "looping on a map using `iter` when `keys` or `values` would do", + deprecation: None, + module: "loops", + }, + Lint { + name: "for_loop_over_option", + group: "correctness", + desc: "for-looping over an `Option`, which is more clearly expressed as an `if let`", + deprecation: None, + module: "loops", + }, + Lint { + name: "for_loop_over_result", + group: "correctness", + desc: "for-looping over a `Result`, which is more clearly expressed as an `if let`", + deprecation: None, + module: "loops", + }, + Lint { + name: "forget_copy", + group: "correctness", + desc: "calls to `std::mem::forget` with a value that implements Copy", + deprecation: None, + module: "drop_forget_ref", + }, + Lint { + name: "forget_ref", + group: "correctness", + desc: "calls to `std::mem::forget` with a reference instead of an owned value", + deprecation: None, + module: "drop_forget_ref", + }, + Lint { + name: "future_not_send", + group: "nursery", + desc: "public Futures must be Send", + deprecation: None, + module: "future_not_send", + }, + Lint { + name: "get_last_with_len", + group: "complexity", + desc: "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler", + deprecation: None, + module: "get_last_with_len", + }, + Lint { + name: "get_unwrap", + group: "restriction", + desc: "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead", + deprecation: None, + module: "methods", + }, + Lint { + name: "identity_conversion", + group: "complexity", + desc: "using always-identical `Into`/`From`/`IntoIter` conversions", + deprecation: None, + module: "identity_conversion", + }, + Lint { + name: "identity_op", + group: "complexity", + desc: "using identity operations, e.g., `x + 0` or `y / 1`", + deprecation: None, + module: "identity_op", + }, + Lint { + name: "if_let_mutex", + group: "correctness", + desc: "locking a `Mutex` in an `if let` block can cause deadlocks", + deprecation: None, + module: "if_let_mutex", + }, + Lint { + name: "if_let_some_result", + group: "style", + desc: "usage of `ok()` in `if let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead", + deprecation: None, + module: "if_let_some_result", + }, + Lint { + name: "if_not_else", + group: "pedantic", + desc: "`if` branches that could be swapped so no negation operation is necessary on the condition", + deprecation: None, + module: "if_not_else", + }, + Lint { + name: "if_same_then_else", + group: "correctness", + desc: "`if` with the same `then` and `else` blocks", + deprecation: None, + module: "copies", + }, + Lint { + name: "ifs_same_cond", + group: "correctness", + desc: "consecutive `if`s with the same condition", + deprecation: None, + module: "copies", + }, + Lint { + name: "implicit_hasher", + group: "pedantic", + desc: "missing generalization over different hashers", + deprecation: None, + module: "types", + }, + Lint { + name: "implicit_return", + group: "restriction", + desc: "use a return statement like `return expr` instead of an expression", + deprecation: None, + module: "implicit_return", + }, + Lint { + name: "implicit_saturating_sub", + group: "pedantic", + desc: "Perform saturating subtraction instead of implicitly checking lower bound of data type", + deprecation: None, + module: "implicit_saturating_sub", + }, + Lint { + name: "imprecise_flops", + group: "nursery", + desc: "usage of imprecise floating point operations", + deprecation: None, + module: "floating_point_arithmetic", + }, + Lint { + name: "inconsistent_digit_grouping", + group: "style", + desc: "integer literals with digits grouped inconsistently", + deprecation: None, + module: "literal_representation", + }, + Lint { + name: "indexing_slicing", + group: "restriction", + desc: "indexing/slicing usage", + deprecation: None, + module: "indexing_slicing", + }, + Lint { + name: "ineffective_bit_mask", + group: "correctness", + desc: "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`", + deprecation: None, + module: "bit_mask", + }, + Lint { + name: "inefficient_to_string", + group: "pedantic", + desc: "using `to_string` on `&&T` where `T: ToString`", + deprecation: None, + module: "methods", + }, + Lint { + name: "infallible_destructuring_match", + group: "style", + desc: "a `match` statement with a single infallible arm instead of a `let`", + deprecation: None, + module: "matches", + }, + Lint { + name: "infinite_iter", + group: "correctness", + desc: "infinite iteration", + deprecation: None, + module: "infinite_iter", + }, + Lint { + name: "inherent_to_string", + group: "style", + desc: "type implements inherent method `to_string()`, but should instead implement the `Display` trait", + deprecation: None, + module: "inherent_to_string", + }, + Lint { + name: "inherent_to_string_shadow_display", + group: "correctness", + desc: "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait", + deprecation: None, + module: "inherent_to_string", + }, + Lint { + name: "inline_always", + group: "pedantic", + desc: "use of `#[inline(always)]`", + deprecation: None, + module: "attrs", + }, + Lint { + name: "inline_fn_without_body", + group: "correctness", + desc: "use of `#[inline]` on trait methods without bodies", + deprecation: None, + module: "inline_fn_without_body", + }, + Lint { + name: "int_plus_one", + group: "complexity", + desc: "instead of using `x >= y + 1`, use `x > y`", + deprecation: None, + module: "int_plus_one", + }, + Lint { + name: "integer_arithmetic", + group: "restriction", + desc: "any integer arithmetic expression which could overflow or panic", + deprecation: None, + module: "arithmetic", + }, + Lint { + name: "integer_division", + group: "restriction", + desc: "integer division may cause loss of precision", + deprecation: None, + module: "integer_division", + }, + Lint { + name: "into_iter_on_ref", + group: "style", + desc: "using `.into_iter()` on a reference", + deprecation: None, + module: "methods", + }, + Lint { + name: "invalid_atomic_ordering", + group: "correctness", + desc: "usage of invalid atomic ordering in atomic loads/stores and memory fences", + deprecation: None, + module: "atomic_ordering", + }, + Lint { + name: "invalid_regex", + group: "correctness", + desc: "invalid regular expressions", + deprecation: None, + module: "regex", + }, + Lint { + name: "invalid_upcast_comparisons", + group: "pedantic", + desc: "a comparison involving an upcast which is always true or false", + deprecation: None, + module: "types", + }, + Lint { + name: "items_after_statements", + group: "pedantic", + desc: "blocks where an item comes after a statement", + deprecation: None, + module: "items_after_statements", + }, + Lint { + name: "iter_cloned_collect", + group: "style", + desc: "using `.cloned().collect()` on slice to create a `Vec`", + deprecation: None, + module: "methods", + }, + Lint { + name: "iter_next_loop", + group: "correctness", + desc: "for-looping over `_.next()` which is probably not intended", + deprecation: None, + module: "loops", + }, + Lint { + name: "iter_nth", + group: "perf", + desc: "using `.iter().nth()` on a standard library type with O(1) element access", + deprecation: None, + module: "methods", + }, + Lint { + name: "iter_nth_zero", + group: "style", + desc: "replace `iter.nth(0)` with `iter.next()`", + deprecation: None, + module: "methods", + }, + Lint { + name: "iter_skip_next", + group: "style", + desc: "using `.skip(x).next()` on an iterator", + deprecation: None, + module: "methods", + }, + Lint { + name: "iterator_step_by_zero", + group: "correctness", + desc: "using `Iterator::step_by(0)`, which will panic at runtime", + deprecation: None, + module: "methods", + }, + Lint { + name: "just_underscores_and_digits", + group: "style", + desc: "unclear name", + deprecation: None, + module: "non_expressive_names", + }, + Lint { + name: "large_const_arrays", + group: "perf", + desc: "large non-scalar const array may cause performance overhead", + deprecation: None, + module: "large_const_arrays", + }, + Lint { + name: "large_digit_groups", + group: "pedantic", + desc: "grouping digits into groups that are too large", + deprecation: None, + module: "literal_representation", + }, + Lint { + name: "large_enum_variant", + group: "perf", + desc: "large size difference between variants on an enum", + deprecation: None, + module: "large_enum_variant", + }, + Lint { + name: "large_stack_arrays", + group: "pedantic", + desc: "allocating large arrays on stack may cause stack overflow", + deprecation: None, + module: "large_stack_arrays", + }, + Lint { + name: "len_without_is_empty", + group: "style", + desc: "traits or impls with a public `len` method but no corresponding `is_empty` method", + deprecation: None, + module: "len_zero", + }, + Lint { + name: "len_zero", + group: "style", + desc: "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead", + deprecation: None, + module: "len_zero", + }, + Lint { + name: "let_and_return", + group: "style", + desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block", + deprecation: None, + module: "returns", + }, + Lint { + name: "let_underscore_lock", + group: "correctness", + desc: "non-binding let on a synchronization lock", + deprecation: None, + module: "let_underscore", + }, + Lint { + name: "let_underscore_must_use", + group: "restriction", + desc: "non-binding let on a `#[must_use]` expression", + deprecation: None, + module: "let_underscore", + }, + Lint { + name: "let_unit_value", + group: "pedantic", + desc: "creating a `let` binding to a value of unit type, which usually can\'t be used afterwards", + deprecation: None, + module: "types", + }, + Lint { + name: "linkedlist", + group: "pedantic", + desc: "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`", + deprecation: None, + module: "types", + }, + Lint { + name: "logic_bug", + group: "correctness", + desc: "boolean expressions that contain terminals which can be eliminated", + deprecation: None, + module: "booleans", + }, + Lint { + name: "lossy_float_literal", + group: "restriction", + desc: "lossy whole number float literals", + deprecation: None, + module: "float_literal", + }, + Lint { + name: "macro_use_imports", + group: "pedantic", + desc: "#[macro_use] is no longer needed", + deprecation: None, + module: "macro_use", + }, + Lint { + name: "main_recursion", + group: "style", + desc: "recursion using the entrypoint", + deprecation: None, + module: "main_recursion", + }, + Lint { + name: "manual_memcpy", + group: "perf", + desc: "manually copying items between slices", + deprecation: None, + module: "loops", + }, + Lint { + name: "manual_saturating_arithmetic", + group: "style", + desc: "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "manual_swap", + group: "complexity", + desc: "manual swap of two variables", + deprecation: None, + module: "swap", + }, + Lint { + name: "many_single_char_names", + group: "style", + desc: "too many single character bindings", + deprecation: None, + module: "non_expressive_names", + }, + Lint { + name: "map_clone", + group: "style", + desc: "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types", + deprecation: None, + module: "map_clone", + }, + Lint { + name: "map_entry", + group: "perf", + desc: "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`", + deprecation: None, + module: "entry", + }, + Lint { + name: "map_flatten", + group: "pedantic", + desc: "using combinations of `flatten` and `map` which can usually be written as a single method call", + deprecation: None, + module: "methods", + }, + Lint { + name: "match_as_ref", + group: "complexity", + desc: "a `match` on an Option value instead of using `as_ref()` or `as_mut`", + deprecation: None, + module: "matches", + }, + Lint { + name: "match_bool", + group: "pedantic", + desc: "a `match` on a boolean expression instead of an `if..else` block", + deprecation: None, + module: "matches", + }, + Lint { + name: "match_on_vec_items", + group: "correctness", + desc: "matching on vector elements can panic", + deprecation: None, + module: "match_on_vec_items", + }, + Lint { + name: "match_overlapping_arm", + group: "style", + desc: "a `match` with overlapping arms", + deprecation: None, + module: "matches", + }, + Lint { + name: "match_ref_pats", + group: "style", + desc: "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression", + deprecation: None, + module: "matches", + }, + Lint { + name: "match_same_arms", + group: "pedantic", + desc: "`match` with identical arm bodies", + deprecation: None, + module: "copies", + }, + Lint { + name: "match_single_binding", + group: "complexity", + desc: "a match with a single binding instead of using `let` statement", + deprecation: None, + module: "matches", + }, + Lint { + name: "match_wild_err_arm", + group: "style", + desc: "a `match` with `Err(_)` arm and take drastic actions", + deprecation: None, + module: "matches", + }, + Lint { + name: "maybe_infinite_iter", + group: "pedantic", + desc: "possible infinite iteration", + deprecation: None, + module: "infinite_iter", + }, + Lint { + name: "mem_discriminant_non_enum", + group: "correctness", + desc: "calling `mem::descriminant` on non-enum type", + deprecation: None, + module: "mem_discriminant", + }, + Lint { + name: "mem_forget", + group: "restriction", + desc: "`mem::forget` usage on `Drop` types, likely to cause memory leaks", + deprecation: None, + module: "mem_forget", + }, + Lint { + name: "mem_replace_option_with_none", + group: "style", + desc: "replacing an `Option` with `None` instead of `take()`", + deprecation: None, + module: "mem_replace", + }, + Lint { + name: "mem_replace_with_default", + group: "style", + desc: "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`", + deprecation: None, + module: "mem_replace", + }, + Lint { + name: "mem_replace_with_uninit", + group: "correctness", + desc: "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`", + deprecation: None, + module: "mem_replace", + }, + Lint { + name: "min_max", + group: "correctness", + desc: "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant", + deprecation: None, + module: "minmax", + }, + Lint { + name: "mismatched_target_os", + group: "correctness", + desc: "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`", + deprecation: None, + module: "attrs", + }, + Lint { + name: "misrefactored_assign_op", + group: "complexity", + desc: "having a variable on both sides of an assign op", + deprecation: None, + module: "assign_ops", + }, + Lint { + name: "missing_const_for_fn", + group: "nursery", + desc: "Lint functions definitions that could be made `const fn`", + deprecation: None, + module: "missing_const_for_fn", + }, + Lint { + name: "missing_docs_in_private_items", + group: "restriction", + desc: "detects missing documentation for public and private members", + deprecation: None, + module: "missing_doc", + }, + Lint { + name: "missing_errors_doc", + group: "pedantic", + desc: "`pub fn` returns `Result` without `# Errors` in doc comment", + deprecation: None, + module: "doc", + }, + Lint { + name: "missing_inline_in_public_items", + group: "restriction", + desc: "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)", + deprecation: None, + module: "missing_inline", + }, + Lint { + name: "missing_safety_doc", + group: "style", + desc: "`pub unsafe fn` without `# Safety` docs", + deprecation: None, + module: "doc", + }, + Lint { + name: "mistyped_literal_suffixes", + group: "correctness", + desc: "mistyped literal suffix", + deprecation: None, + module: "literal_representation", + }, + Lint { + name: "mixed_case_hex_literals", + group: "style", + desc: "hex literals whose letter digits are not consistently upper- or lowercased", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "module_inception", + group: "style", + desc: "modules that have the same name as their parent module", + deprecation: None, + module: "enum_variants", + }, + Lint { + name: "module_name_repetitions", + group: "pedantic", + desc: "type names prefixed/postfixed with their containing module\'s name", + deprecation: None, + module: "enum_variants", + }, + Lint { + name: "modulo_arithmetic", + group: "restriction", + desc: "any modulo arithmetic statement", + deprecation: None, + module: "modulo_arithmetic", + }, + Lint { + name: "modulo_one", + group: "correctness", + desc: "taking a number modulo 1, which always returns 0", + deprecation: None, + module: "misc", + }, + Lint { + name: "multiple_crate_versions", + group: "cargo", + desc: "multiple versions of the same crate being used", + deprecation: None, + module: "multiple_crate_versions", + }, + Lint { + name: "multiple_inherent_impl", + group: "restriction", + desc: "Multiple inherent impl that could be grouped", + deprecation: None, + module: "inherent_impl", + }, + Lint { + name: "must_use_candidate", + group: "pedantic", + desc: "function or method that could take a `#[must_use]` attribute", + deprecation: None, + module: "functions", + }, + Lint { + name: "must_use_unit", + group: "style", + desc: "`#[must_use]` attribute on a unit-returning function / method", + deprecation: None, + module: "functions", + }, + Lint { + name: "mut_from_ref", + group: "correctness", + desc: "fns that create mutable refs from immutable ref args", + deprecation: None, + module: "ptr", + }, + Lint { + name: "mut_mut", + group: "pedantic", + desc: "usage of double-mut refs, e.g., `&mut &mut ...`", + deprecation: None, + module: "mut_mut", + }, + Lint { + name: "mut_range_bound", + group: "complexity", + desc: "for loop over a range where one of the bounds is a mutable variable", + deprecation: None, + module: "loops", + }, + Lint { + name: "mutable_key_type", + group: "correctness", + desc: "Check for mutable `Map`/`Set` key type", + deprecation: None, + module: "mut_key", + }, + Lint { + name: "mutex_atomic", + group: "perf", + desc: "using a mutex where an atomic value could be used instead", + deprecation: None, + module: "mutex_atomic", + }, + Lint { + name: "mutex_integer", + group: "nursery", + desc: "using a mutex for an integer type", + deprecation: None, + module: "mutex_atomic", + }, + Lint { + name: "naive_bytecount", + group: "perf", + desc: "use of naive `.filter(|&x| x == y).count()` to count byte values", + deprecation: None, + module: "bytecount", + }, + Lint { + name: "needless_bool", + group: "complexity", + desc: "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`", + deprecation: None, + module: "needless_bool", + }, + Lint { + name: "needless_borrow", + group: "nursery", + desc: "taking a reference that is going to be automatically dereferenced", + deprecation: None, + module: "needless_borrow", + }, + Lint { + name: "needless_borrowed_reference", + group: "complexity", + desc: "taking a needless borrowed reference", + deprecation: None, + module: "needless_borrowed_ref", + }, + Lint { + name: "needless_collect", + group: "perf", + desc: "collecting an iterator when collect is not needed", + deprecation: None, + module: "loops", + }, + Lint { + name: "needless_continue", + group: "pedantic", + desc: "`continue` statements that can be replaced by a rearrangement of code", + deprecation: None, + module: "needless_continue", + }, + Lint { + name: "needless_doctest_main", + group: "style", + desc: "presence of `fn main() {` in code examples", + deprecation: None, + module: "doc", + }, + Lint { + name: "needless_lifetimes", + group: "complexity", + desc: "using explicit lifetimes for references in function arguments when elision rules would allow omitting them", + deprecation: None, + module: "lifetimes", + }, + Lint { + name: "needless_pass_by_value", + group: "pedantic", + desc: "functions taking arguments by value, but not consuming them in its body", + deprecation: None, + module: "needless_pass_by_value", + }, + Lint { + name: "needless_range_loop", + group: "style", + desc: "for-looping over a range of indices where an iterator over items would do", + deprecation: None, + module: "loops", + }, + Lint { + name: "needless_return", + group: "style", + desc: "using a return statement like `return expr;` where an expression would suffice", + deprecation: None, + module: "returns", + }, + Lint { + name: "needless_update", + group: "complexity", + desc: "using `Foo { ..base }` when there are no missing fields", + deprecation: None, + module: "needless_update", + }, + Lint { + name: "neg_cmp_op_on_partial_ord", + group: "complexity", + desc: "The use of negated comparison operators on partially ordered types may produce confusing code.", + deprecation: None, + module: "neg_cmp_op_on_partial_ord", + }, + Lint { + name: "neg_multiply", + group: "style", + desc: "multiplying integers with `-1`", + deprecation: None, + module: "neg_multiply", + }, + Lint { + name: "never_loop", + group: "correctness", + desc: "any loop that will always `break` or `return`", + deprecation: None, + module: "loops", + }, + Lint { + name: "new_ret_no_self", + group: "style", + desc: "not returning type containing `Self` in a `new` method", + deprecation: None, + module: "methods", + }, + Lint { + name: "new_without_default", + group: "style", + desc: "`fn new() -> Self` method without `Default` implementation", + deprecation: None, + module: "new_without_default", + }, + Lint { + name: "no_effect", + group: "complexity", + desc: "statements with no effect", + deprecation: None, + module: "no_effect", + }, + Lint { + name: "non_ascii_literal", + group: "pedantic", + desc: "using any literal non-ASCII chars in a string literal instead of using the `\\\\u` escape", + deprecation: None, + module: "unicode", + }, + Lint { + name: "nonminimal_bool", + group: "complexity", + desc: "boolean expressions that can be written more concisely", + deprecation: None, + module: "booleans", + }, + Lint { + name: "nonsensical_open_options", + group: "correctness", + desc: "nonsensical combination of options for opening a file", + deprecation: None, + module: "open_options", + }, + Lint { + name: "not_unsafe_ptr_arg_deref", + group: "correctness", + desc: "public functions dereferencing raw pointer arguments but not marked `unsafe`", + deprecation: None, + module: "functions", + }, + Lint { + name: "ok_expect", + group: "style", + desc: "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result", + deprecation: None, + module: "methods", + }, + Lint { + name: "op_ref", + group: "style", + desc: "taking a reference to satisfy the type constraints on `==`", + deprecation: None, + module: "eq_op", + }, + Lint { + name: "option_and_then_some", + group: "complexity", + desc: "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "option_as_ref_deref", + group: "complexity", + desc: "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`", + deprecation: None, + module: "methods", + }, + Lint { + name: "option_env_unwrap", + group: "correctness", + desc: "using `option_env!(...).unwrap()` to get environment variable", + deprecation: None, + module: "option_env_unwrap", + }, + Lint { + name: "option_expect_used", + group: "restriction", + desc: "using `Option.expect()`, which might be better handled", + deprecation: None, + module: "methods", + }, + Lint { + name: "option_map_or_none", + group: "style", + desc: "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "option_map_unit_fn", + group: "complexity", + desc: "using `option.map(f)`, where `f` is a function or closure that returns `()`", + deprecation: None, + module: "map_unit_fn", + }, + Lint { + name: "option_map_unwrap_or", + group: "pedantic", + desc: "using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "option_map_unwrap_or_else", + group: "pedantic", + desc: "using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "option_option", + group: "pedantic", + desc: "usage of `Option>`", + deprecation: None, + module: "types", + }, + Lint { + name: "option_unwrap_used", + group: "restriction", + desc: "using `Option.unwrap()`, which should at least get a better message using `expect()`", + deprecation: None, + module: "methods", + }, + Lint { + name: "or_fun_call", + group: "perf", + desc: "using any `*or` method with a function call, which suggests `*or_else`", + deprecation: None, + module: "methods", + }, + Lint { + name: "out_of_bounds_indexing", + group: "correctness", + desc: "out of bounds constant indexing", + deprecation: None, + module: "indexing_slicing", + }, + Lint { + name: "overflow_check_conditional", + group: "complexity", + desc: "overflow checks inspired by C which are likely to panic", + deprecation: None, + module: "overflow_check_conditional", + }, + Lint { + name: "panic", + group: "restriction", + desc: "usage of the `panic!` macro", + deprecation: None, + module: "panic_unimplemented", + }, + Lint { + name: "panic_params", + group: "style", + desc: "missing parameters in `panic!` calls", + deprecation: None, + module: "panic_unimplemented", + }, + Lint { + name: "panicking_unwrap", + group: "correctness", + desc: "checks for calls of `unwrap[_err]()` that will always fail", + deprecation: None, + module: "unwrap", + }, + Lint { + name: "partialeq_ne_impl", + group: "complexity", + desc: "re-implementing `PartialEq::ne`", + deprecation: None, + module: "partialeq_ne_impl", + }, + Lint { + name: "path_buf_push_overwrite", + group: "nursery", + desc: "calling `push` with file system root on `PathBuf` can overwrite it", + deprecation: None, + module: "path_buf_push_overwrite", + }, + Lint { + name: "possible_missing_comma", + group: "correctness", + desc: "possible missing comma in array", + deprecation: None, + module: "formatting", + }, + Lint { + name: "precedence", + group: "complexity", + desc: "operations where precedence may be unclear", + deprecation: None, + module: "precedence", + }, + Lint { + name: "print_literal", + group: "style", + desc: "printing a literal with a format string", + deprecation: None, + module: "write", + }, + Lint { + name: "print_stdout", + group: "restriction", + desc: "printing on stdout", + deprecation: None, + module: "write", + }, + Lint { + name: "print_with_newline", + group: "style", + desc: "using `print!()` with a format string that ends in a single newline", + deprecation: None, + module: "write", + }, + Lint { + name: "println_empty_string", + group: "style", + desc: "using `println!(\"\")` with an empty string", + deprecation: None, + module: "write", + }, + Lint { + name: "ptr_arg", + group: "style", + desc: "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively", + deprecation: None, + module: "ptr", + }, + Lint { + name: "ptr_offset_with_cast", + group: "complexity", + desc: "unneeded pointer offset cast", + deprecation: None, + module: "ptr_offset_with_cast", + }, + Lint { + name: "pub_enum_variant_names", + group: "pedantic", + desc: "enums where all variants share a prefix/postfix", + deprecation: None, + module: "enum_variants", + }, + Lint { + name: "question_mark", + group: "style", + desc: "checks for expressions that could be replaced by the question mark operator", + deprecation: None, + module: "question_mark", + }, + Lint { + name: "range_minus_one", + group: "complexity", + desc: "`x..=(y-1)` reads better as `x..y`", + deprecation: None, + module: "ranges", + }, + Lint { + name: "range_plus_one", + group: "pedantic", + desc: "`x..(y+1)` reads better as `x..=y`", + deprecation: None, + module: "ranges", + }, + Lint { + name: "range_zip_with_len", + group: "complexity", + desc: "zipping iterator with a range when `enumerate()` would do", + deprecation: None, + module: "ranges", + }, + Lint { + name: "redundant_allocation", + group: "perf", + desc: "redundant allocation", + deprecation: None, + module: "types", + }, + Lint { + name: "redundant_clone", + group: "perf", + desc: "`clone()` of an owned value that is going to be dropped immediately", + deprecation: None, + module: "redundant_clone", + }, + Lint { + name: "redundant_closure", + group: "style", + desc: "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)", + deprecation: None, + module: "eta_reduction", + }, + Lint { + name: "redundant_closure_call", + group: "complexity", + desc: "throwaway closures called in the expression they are defined", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "redundant_closure_for_method_calls", + group: "pedantic", + desc: "redundant closures for method calls", + deprecation: None, + module: "eta_reduction", + }, + Lint { + name: "redundant_field_names", + group: "style", + desc: "checks for fields in struct literals where shorthands could be used", + deprecation: None, + module: "redundant_field_names", + }, + Lint { + name: "redundant_pattern", + group: "style", + desc: "using `name @ _` in a pattern", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "redundant_pattern_matching", + group: "style", + desc: "use the proper utility function avoiding an `if let`", + deprecation: None, + module: "redundant_pattern_matching", + }, + Lint { + name: "redundant_pub_crate", + group: "nursery", + desc: "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them.", + deprecation: None, + module: "redundant_pub_crate", + }, + Lint { + name: "redundant_static_lifetimes", + group: "style", + desc: "Using explicit `\'static` lifetime for constants or statics when elision rules would allow omitting them.", + deprecation: None, + module: "redundant_static_lifetimes", + }, + Lint { + name: "ref_in_deref", + group: "complexity", + desc: "Use of reference in auto dereference expression.", + deprecation: None, + module: "reference", + }, + Lint { + name: "regex_macro", + group: "style", + desc: "use of `regex!(_)` instead of `Regex::new(_)`", + deprecation: None, + module: "regex", + }, + Lint { + name: "rest_pat_in_fully_bound_structs", + group: "restriction", + desc: "a match on a struct that binds all fields but still uses the wildcard pattern", + deprecation: None, + module: "matches", + }, + Lint { + name: "result_expect_used", + group: "restriction", + desc: "using `Result.expect()`, which might be better handled", + deprecation: None, + module: "methods", + }, + Lint { + name: "result_map_or_into_option", + group: "style", + desc: "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`", + deprecation: None, + module: "methods", + }, + Lint { + name: "result_map_unit_fn", + group: "complexity", + desc: "using `result.map(f)`, where `f` is a function or closure that returns `()`", + deprecation: None, + module: "map_unit_fn", + }, + Lint { + name: "result_map_unwrap_or_else", + group: "pedantic", + desc: "using `Result.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `.map_or_else(g, f)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "result_unwrap_used", + group: "restriction", + desc: "using `Result.unwrap()`, which might be better handled", + deprecation: None, + module: "methods", + }, + Lint { + name: "reverse_range_loop", + group: "correctness", + desc: "iteration over an empty range, such as `10..0` or `5..5`", + deprecation: None, + module: "loops", + }, + Lint { + name: "same_functions_in_if_condition", + group: "pedantic", + desc: "consecutive `if`s with the same function call", + deprecation: None, + module: "copies", + }, + Lint { + name: "search_is_some", + group: "complexity", + desc: "using an iterator search followed by `is_some()`, which is more succinctly expressed as a call to `any()`", + deprecation: None, + module: "methods", + }, + Lint { + name: "serde_api_misuse", + group: "correctness", + desc: "various things that will negatively affect your serde experience", + deprecation: None, + module: "serde_api", + }, + Lint { + name: "shadow_reuse", + group: "restriction", + desc: "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`", + deprecation: None, + module: "shadow", + }, + Lint { + name: "shadow_same", + group: "restriction", + desc: "rebinding a name to itself, e.g., `let mut x = &mut x`", + deprecation: None, + module: "shadow", + }, + Lint { + name: "shadow_unrelated", + group: "pedantic", + desc: "rebinding a name without even using the original value", + deprecation: None, + module: "shadow", + }, + Lint { + name: "short_circuit_statement", + group: "complexity", + desc: "using a short circuit boolean condition as a statement", + deprecation: None, + module: "misc", + }, + Lint { + name: "should_implement_trait", + group: "style", + desc: "defining a method that should be implementing a std trait", + deprecation: None, + module: "methods", + }, + Lint { + name: "similar_names", + group: "pedantic", + desc: "similarly named items and bindings", + deprecation: None, + module: "non_expressive_names", + }, + Lint { + name: "single_char_pattern", + group: "perf", + desc: "using a single-character str where a char could be used, e.g., `_.split(\"x\")`", + deprecation: None, + module: "methods", + }, + Lint { + name: "single_component_path_imports", + group: "style", + desc: "imports with single component path are redundant", + deprecation: None, + module: "single_component_path_imports", + }, + Lint { + name: "single_match", + group: "style", + desc: "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`", + deprecation: None, + module: "matches", + }, + Lint { + name: "single_match_else", + group: "pedantic", + desc: "a `match` statement with two arms where the second arm\'s pattern is a placeholder instead of a specific match pattern", + deprecation: None, + module: "matches", + }, + Lint { + name: "skip_while_next", + group: "complexity", + desc: "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`", + deprecation: None, + module: "methods", + }, + Lint { + name: "slow_vector_initialization", + group: "perf", + desc: "slow vector initialization", + deprecation: None, + module: "slow_vector_initialization", + }, + Lint { + name: "string_add", + group: "restriction", + desc: "using `x + ..` where x is a `String` instead of `push_str()`", + deprecation: None, + module: "strings", + }, + Lint { + name: "string_add_assign", + group: "pedantic", + desc: "using `x = x + ..` where x is a `String` instead of `push_str()`", + deprecation: None, + module: "strings", + }, + Lint { + name: "string_extend_chars", + group: "style", + desc: "using `x.extend(s.chars())` where s is a `&str` or `String`", + deprecation: None, + module: "methods", + }, + Lint { + name: "string_lit_as_bytes", + group: "style", + desc: "calling `as_bytes` on a string literal instead of using a byte string literal", + deprecation: None, + module: "strings", + }, + Lint { + name: "struct_excessive_bools", + group: "pedantic", + desc: "using too many bools in a struct", + deprecation: None, + module: "excessive_bools", + }, + Lint { + name: "suboptimal_flops", + group: "nursery", + desc: "usage of sub-optimal floating point operations", + deprecation: None, + module: "floating_point_arithmetic", + }, + Lint { + name: "suspicious_arithmetic_impl", + group: "correctness", + desc: "suspicious use of operators in impl of arithmetic trait", + deprecation: None, + module: "suspicious_trait_impl", + }, + Lint { + name: "suspicious_assignment_formatting", + group: "style", + desc: "suspicious formatting of `*=`, `-=` or `!=`", + deprecation: None, + module: "formatting", + }, + Lint { + name: "suspicious_else_formatting", + group: "style", + desc: "suspicious formatting of `else`", + deprecation: None, + module: "formatting", + }, + Lint { + name: "suspicious_map", + group: "complexity", + desc: "suspicious usage of map", + deprecation: None, + module: "methods", + }, + Lint { + name: "suspicious_op_assign_impl", + group: "correctness", + desc: "suspicious use of operators in impl of OpAssign trait", + deprecation: None, + module: "suspicious_trait_impl", + }, + Lint { + name: "suspicious_unary_op_formatting", + group: "style", + desc: "suspicious formatting of unary `-` or `!` on the RHS of a BinOp", + deprecation: None, + module: "formatting", + }, + Lint { + name: "tabs_in_doc_comments", + group: "style", + desc: "using tabs in doc comments is not recommended", + deprecation: None, + module: "tabs_in_doc_comments", + }, + Lint { + name: "temporary_assignment", + group: "complexity", + desc: "assignments to temporaries", + deprecation: None, + module: "temporary_assignment", + }, + Lint { + name: "temporary_cstring_as_ptr", + group: "correctness", + desc: "getting the inner pointer of a temporary `CString`", + deprecation: None, + module: "methods", + }, + Lint { + name: "to_digit_is_some", + group: "style", + desc: "`char.is_digit()` is clearer", + deprecation: None, + module: "to_digit_is_some", + }, + Lint { + name: "todo", + group: "restriction", + desc: "`todo!` should not be present in production code", + deprecation: None, + module: "panic_unimplemented", + }, + Lint { + name: "too_many_arguments", + group: "complexity", + desc: "functions with too many arguments", + deprecation: None, + module: "functions", + }, + Lint { + name: "too_many_lines", + group: "pedantic", + desc: "functions with too many lines", + deprecation: None, + module: "functions", + }, + Lint { + name: "toplevel_ref_arg", + group: "style", + desc: "an entire binding declared as `ref`, in a function argument or a `let` statement", + deprecation: None, + module: "misc", + }, + Lint { + name: "transmute_bytes_to_str", + group: "complexity", + desc: "transmutes from a `&[u8]` to a `&str`", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmute_float_to_int", + group: "complexity", + desc: "transmutes from a float to an integer", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmute_int_to_bool", + group: "complexity", + desc: "transmutes from an integer to a `bool`", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmute_int_to_char", + group: "complexity", + desc: "transmutes from an integer to a `char`", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmute_int_to_float", + group: "complexity", + desc: "transmutes from an integer to a float", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmute_ptr_to_ptr", + group: "complexity", + desc: "transmutes from a pointer to a pointer / a reference to a reference", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmute_ptr_to_ref", + group: "complexity", + desc: "transmutes from a pointer to a reference type", + deprecation: None, + module: "transmute", + }, + Lint { + name: "transmuting_null", + group: "correctness", + desc: "transmutes from a null pointer to a reference, which is undefined behavior", + deprecation: None, + module: "transmuting_null", + }, + Lint { + name: "trivial_regex", + group: "style", + desc: "trivial regular expressions", + deprecation: None, + module: "regex", + }, + Lint { + name: "trivially_copy_pass_by_ref", + group: "pedantic", + desc: "functions taking small copyable arguments by reference", + deprecation: None, + module: "trivially_copy_pass_by_ref", + }, + Lint { + name: "try_err", + group: "style", + desc: "return errors explicitly rather than hiding them behind a `?`", + deprecation: None, + module: "try_err", + }, + Lint { + name: "type_complexity", + group: "complexity", + desc: "usage of very complex types that might be better factored into `type` definitions", + deprecation: None, + module: "types", + }, + Lint { + name: "type_repetition_in_bounds", + group: "pedantic", + desc: "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`", + deprecation: None, + module: "trait_bounds", + }, + Lint { + name: "unicode_not_nfc", + group: "pedantic", + desc: "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)", + deprecation: None, + module: "unicode", + }, + Lint { + name: "unimplemented", + group: "restriction", + desc: "`unimplemented!` should not be present in production code", + deprecation: None, + module: "panic_unimplemented", + }, + Lint { + name: "uninit_assumed_init", + group: "correctness", + desc: "`MaybeUninit::uninit().assume_init()`", + deprecation: None, + module: "methods", + }, + Lint { + name: "unit_arg", + group: "complexity", + desc: "passing unit to a function", + deprecation: None, + module: "types", + }, + Lint { + name: "unit_cmp", + group: "correctness", + desc: "comparing unit values", + deprecation: None, + module: "types", + }, + Lint { + name: "unknown_clippy_lints", + group: "style", + desc: "unknown_lints for scoped Clippy lints", + deprecation: None, + module: "attrs", + }, + Lint { + name: "unnecessary_cast", + group: "complexity", + desc: "cast to the same type, e.g., `x as i32` where `x: i32`", + deprecation: None, + module: "types", + }, + Lint { + name: "unnecessary_filter_map", + group: "complexity", + desc: "using `filter_map` when a more succinct alternative exists", + deprecation: None, + module: "methods", + }, + Lint { + name: "unnecessary_fold", + group: "style", + desc: "using `fold` when a more succinct alternative exists", + deprecation: None, + module: "methods", + }, + Lint { + name: "unnecessary_mut_passed", + group: "style", + desc: "an argument passed as a mutable reference although the callee only demands an immutable reference", + deprecation: None, + module: "mut_reference", + }, + Lint { + name: "unnecessary_operation", + group: "complexity", + desc: "outer expressions with no effect", + deprecation: None, + module: "no_effect", + }, + Lint { + name: "unnecessary_unwrap", + group: "complexity", + desc: "checks for calls of `unwrap[_err]()` that cannot fail", + deprecation: None, + module: "unwrap", + }, + Lint { + name: "unneeded_field_pattern", + group: "restriction", + desc: "struct fields bound to a wildcard instead of using `..`", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "unneeded_wildcard_pattern", + group: "complexity", + desc: "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "unreachable", + group: "restriction", + desc: "`unreachable!` should not be present in production code", + deprecation: None, + module: "panic_unimplemented", + }, + Lint { + name: "unreadable_literal", + group: "pedantic", + desc: "long integer literal without underscores", + deprecation: None, + module: "literal_representation", + }, + Lint { + name: "unsafe_derive_deserialize", + group: "pedantic", + desc: "deriving `serde::Deserialize` on a type that has methods using `unsafe`", + deprecation: None, + module: "derive", + }, + Lint { + name: "unsafe_removed_from_name", + group: "style", + desc: "`unsafe` removed from API names on import", + deprecation: None, + module: "unsafe_removed_from_name", + }, + Lint { + name: "unseparated_literal_suffix", + group: "pedantic", + desc: "literals whose suffix is not separated by an underscore", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "unsound_collection_transmute", + group: "correctness", + desc: "transmute between collections of layout-incompatible types", + deprecation: None, + module: "transmute", + }, + Lint { + name: "unused_io_amount", + group: "correctness", + desc: "unused written/read amount", + deprecation: None, + module: "unused_io_amount", + }, + Lint { + name: "unused_self", + group: "pedantic", + desc: "methods that contain a `self` argument but don\'t use it", + deprecation: None, + module: "unused_self", + }, + Lint { + name: "unused_unit", + group: "style", + desc: "needless unit expression", + deprecation: None, + module: "returns", + }, + Lint { + name: "use_debug", + group: "restriction", + desc: "use of `Debug`-based formatting", + deprecation: None, + module: "write", + }, + Lint { + name: "use_self", + group: "nursery", + desc: "Unnecessary structure name repetition whereas `Self` is applicable", + deprecation: None, + module: "use_self", + }, + Lint { + name: "used_underscore_binding", + group: "pedantic", + desc: "using a binding which is prefixed with an underscore", + deprecation: None, + module: "misc", + }, + Lint { + name: "useless_asref", + group: "complexity", + desc: "using `as_ref` where the types before and after the call are the same", + deprecation: None, + module: "methods", + }, + Lint { + name: "useless_attribute", + group: "correctness", + desc: "use of lint attributes on `extern crate` items", + deprecation: None, + module: "attrs", + }, + Lint { + name: "useless_format", + group: "complexity", + desc: "useless use of `format!`", + deprecation: None, + module: "format", + }, + Lint { + name: "useless_let_if_seq", + group: "style", + desc: "unidiomatic `let mut` declaration followed by initialization in `if`", + deprecation: None, + module: "let_if_seq", + }, + Lint { + name: "useless_transmute", + group: "nursery", + desc: "transmutes that have the same to and from types or could be a cast/coercion", + deprecation: None, + module: "transmute", + }, + Lint { + name: "useless_vec", + group: "perf", + desc: "useless `vec!`", + deprecation: None, + module: "vec", + }, + Lint { + name: "vec_box", + group: "complexity", + desc: "usage of `Vec>` where T: Sized, vector elements are already on the heap", + deprecation: None, + module: "types", + }, + Lint { + name: "verbose_bit_mask", + group: "style", + desc: "expressions where a bit mask is less readable than the corresponding method call", + deprecation: None, + module: "bit_mask", + }, + Lint { + name: "verbose_file_reads", + group: "restriction", + desc: "use of `File::read_to_end` or `File::read_to_string`", + deprecation: None, + module: "verbose_file_reads", + }, + Lint { + name: "vtable_address_comparisons", + group: "correctness", + desc: "comparison with an address of a trait vtable", + deprecation: None, + module: "unnamed_address", + }, + Lint { + name: "while_immutable_condition", + group: "correctness", + desc: "variables used within while expression are not mutated in the body", + deprecation: None, + module: "loops", + }, + Lint { + name: "while_let_loop", + group: "complexity", + desc: "`loop { if let { ... } else break }`, which can be written as a `while let` loop", + deprecation: None, + module: "loops", + }, + Lint { + name: "while_let_on_iterator", + group: "style", + desc: "using a while-let loop instead of a for loop on an iterator", + deprecation: None, + module: "loops", + }, + Lint { + name: "wildcard_dependencies", + group: "cargo", + desc: "wildcard dependencies being used", + deprecation: None, + module: "wildcard_dependencies", + }, + Lint { + name: "wildcard_enum_match_arm", + group: "restriction", + desc: "a wildcard enum match arm using `_`", + deprecation: None, + module: "matches", + }, + Lint { + name: "wildcard_imports", + group: "pedantic", + desc: "lint `use _::*` statements", + deprecation: None, + module: "wildcard_imports", + }, + Lint { + name: "wildcard_in_or_patterns", + group: "complexity", + desc: "a wildcard pattern used with others patterns in same match arm", + deprecation: None, + module: "matches", + }, + Lint { + name: "write_literal", + group: "style", + desc: "writing a literal with a format string", + deprecation: None, + module: "write", + }, + Lint { + name: "write_with_newline", + group: "style", + desc: "using `write!()` with a format string that ends in a single newline", + deprecation: None, + module: "write", + }, + Lint { + name: "writeln_empty_string", + group: "style", + desc: "using `writeln!(buf, \"\")` with an empty string", + deprecation: None, + module: "write", + }, + Lint { + name: "wrong_pub_self_convention", + group: "restriction", + desc: "defining a public method named with an established prefix (like \"into_\") that takes `self` with the wrong convention", + deprecation: None, + module: "methods", + }, + Lint { + name: "wrong_self_convention", + group: "style", + desc: "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention", + deprecation: None, + module: "methods", + }, + Lint { + name: "wrong_transmute", + group: "correctness", + desc: "transmutes that are confusing at best, undefined behaviour at worst and always useless", + deprecation: None, + module: "transmute", + }, + Lint { + name: "zero_divided_by_zero", + group: "complexity", + desc: "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`", + deprecation: None, + module: "zero_div_zero", + }, + Lint { + name: "zero_prefixed_literal", + group: "complexity", + desc: "integer literals starting with `0`", + deprecation: None, + module: "misc_early", + }, + Lint { + name: "zero_ptr", + group: "style", + desc: "using `0 as *{const, mut} T`", + deprecation: None, + module: "misc", + }, + Lint { + name: "zero_width_space", + group: "correctness", + desc: "using a zero-width space in a string literal, which is confusing", + deprecation: None, + module: "unicode", + }, + Lint { + name: "zst_offset", + group: "correctness", + desc: "Check for offset calculations on raw pointers to zero-sized types", + deprecation: None, + module: "methods", + }, +]; +// end lint list, do not remove this comment, it’s used in `update_lints` +} diff --git a/src/tools/clippy/src/main.rs b/src/tools/clippy/src/main.rs new file mode 100644 index 0000000000000..bc43a34ed5d4a --- /dev/null +++ b/src/tools/clippy/src/main.rs @@ -0,0 +1,219 @@ +#![cfg_attr(feature = "deny-warnings", deny(warnings))] + +use rustc_tools_util::VersionInfo; +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::{self, Command}; + +const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code. + +Usage: + cargo clippy [options] [--] [...] + +Common options: + -h, --help Print this message + -V, --version Print version info and exit + +Other options are the same as `cargo check`. + +To allow or deny a lint from the command line you can use `cargo clippy --` +with: + + -W --warn OPT Set lint warnings + -A --allow OPT Set lint allowed + -D --deny OPT Set lint denied + -F --forbid OPT Set lint forbidden + +You can use tool lints to allow or deny lints from your code, eg.: + + #[allow(clippy::needless_lifetimes)] +"#; + +fn show_help() { + println!("{}", CARGO_CLIPPY_HELP); +} + +fn show_version() { + let version_info = rustc_tools_util::get_version_info!(); + println!("{}", version_info); +} + +pub fn main() { + // Check for version and help flags even when invoked as 'cargo-clippy' + if env::args().any(|a| a == "--help" || a == "-h") { + show_help(); + return; + } + + if env::args().any(|a| a == "--version" || a == "-V") { + show_version(); + return; + } + + if let Err(code) = process(env::args().skip(2)) { + process::exit(code); + } +} + +struct ClippyCmd { + unstable_options: bool, + cargo_subcommand: &'static str, + args: Vec, + clippy_args: String, +} + +impl ClippyCmd { + fn new(mut old_args: I) -> Self + where + I: Iterator, + { + let mut cargo_subcommand = "check"; + let mut unstable_options = false; + let mut args = vec![]; + + for arg in old_args.by_ref() { + match arg.as_str() { + "--fix" => { + cargo_subcommand = "fix"; + continue; + }, + "--" => break, + // Cover -Zunstable-options and -Z unstable-options + s if s.ends_with("unstable-options") => unstable_options = true, + _ => {}, + } + + args.push(arg); + } + + if cargo_subcommand == "fix" && !unstable_options { + panic!("Usage of `--fix` requires `-Z unstable-options`"); + } + + // Run the dogfood tests directly on nightly cargo. This is required due + // to a bug in rustup.rs when running cargo on custom toolchains. See issue #3118. + if env::var_os("CLIPPY_DOGFOOD").is_some() && cfg!(windows) { + args.insert(0, "+nightly".to_string()); + } + + let clippy_args: String = old_args.map(|arg| format!("{}__CLIPPY_HACKERY__", arg)).collect(); + + ClippyCmd { + unstable_options, + cargo_subcommand, + args, + clippy_args, + } + } + + fn path_env(&self) -> &'static str { + if self.unstable_options { + "RUSTC_WORKSPACE_WRAPPER" + } else { + "RUSTC_WRAPPER" + } + } + + fn path() -> PathBuf { + let mut path = env::current_exe() + .expect("current executable path invalid") + .with_file_name("clippy-driver"); + + if cfg!(windows) { + path.set_extension("exe"); + } + + path + } + + fn target_dir() -> Option<(&'static str, OsString)> { + env::var_os("CLIPPY_DOGFOOD") + .map(|_| { + env::var_os("CARGO_MANIFEST_DIR").map_or_else( + || std::ffi::OsString::from("clippy_dogfood"), + |d| { + std::path::PathBuf::from(d) + .join("target") + .join("dogfood") + .into_os_string() + }, + ) + }) + .map(|p| ("CARGO_TARGET_DIR", p)) + } + + fn into_std_cmd(self) -> Command { + let mut cmd = Command::new("cargo"); + + cmd.env(self.path_env(), Self::path()) + .envs(ClippyCmd::target_dir()) + .env("CLIPPY_ARGS", self.clippy_args) + .arg(self.cargo_subcommand) + .args(&self.args); + + cmd + } +} + +fn process(old_args: I) -> Result<(), i32> +where + I: Iterator, +{ + let cmd = ClippyCmd::new(old_args); + + let mut cmd = cmd.into_std_cmd(); + + let exit_status = cmd + .spawn() + .expect("could not run cargo") + .wait() + .expect("failed to wait for cargo?"); + + if exit_status.success() { + Ok(()) + } else { + Err(exit_status.code().unwrap_or(-1)) + } +} + +#[cfg(test)] +mod tests { + use super::ClippyCmd; + + #[test] + #[should_panic] + fn fix_without_unstable() { + let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string); + let _ = ClippyCmd::new(args); + } + + #[test] + fn fix_unstable() { + let args = "cargo clippy --fix -Zunstable-options" + .split_whitespace() + .map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert_eq!("fix", cmd.cargo_subcommand); + assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env()); + assert!(cmd.args.iter().any(|arg| arg.ends_with("unstable-options"))); + } + + #[test] + fn check() { + let args = "cargo clippy".split_whitespace().map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert_eq!("check", cmd.cargo_subcommand); + assert_eq!("RUSTC_WRAPPER", cmd.path_env()); + } + + #[test] + fn check_unstable() { + let args = "cargo clippy -Zunstable-options" + .split_whitespace() + .map(ToString::to_string); + let cmd = ClippyCmd::new(args); + assert_eq!("check", cmd.cargo_subcommand); + assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env()); + } +} diff --git a/src/tools/clippy/tests/auxiliary/test_macro.rs b/src/tools/clippy/tests/auxiliary/test_macro.rs new file mode 100644 index 0000000000000..624ca892add31 --- /dev/null +++ b/src/tools/clippy/tests/auxiliary/test_macro.rs @@ -0,0 +1,11 @@ +pub trait A {} + +macro_rules! __implicit_hasher_test_macro { + (impl< $($impl_arg:tt),* > for $kind:ty where $($bounds:tt)*) => { + __implicit_hasher_test_macro!( ($($impl_arg),*) ($kind) ($($bounds)*) ); + }; + + (($($impl_arg:tt)*) ($($kind_arg:tt)*) ($($bounds:tt)*)) => { + impl< $($impl_arg)* > test_macro::A for $($kind_arg)* where $($bounds)* { } + }; +} diff --git a/src/tools/clippy/tests/cargo/mod.rs b/src/tools/clippy/tests/cargo/mod.rs new file mode 100644 index 0000000000000..3c385343f7010 --- /dev/null +++ b/src/tools/clippy/tests/cargo/mod.rs @@ -0,0 +1,29 @@ +use lazy_static::lazy_static; +use std::env; +use std::path::PathBuf; + +lazy_static! { + pub static ref CARGO_TARGET_DIR: PathBuf = { + match env::var_os("CARGO_TARGET_DIR") { + Some(v) => v.into(), + None => env::current_dir().unwrap().join("target"), + } + }; + pub static ref TARGET_LIB: PathBuf = { + if let Some(path) = option_env!("TARGET_LIBS") { + path.into() + } else { + let mut dir = CARGO_TARGET_DIR.clone(); + if let Some(target) = env::var_os("CARGO_BUILD_TARGET") { + dir.push(target); + } + dir.push(env!("PROFILE")); + dir + } + }; +} + +#[must_use] +pub fn is_rustc_test_suite() -> bool { + option_env!("RUSTC_TEST_SUITE").is_some() +} diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs new file mode 100644 index 0000000000000..de2cf6d7873f8 --- /dev/null +++ b/src/tools/clippy/tests/compile-test.rs @@ -0,0 +1,168 @@ +#![feature(test)] // compiletest_rs requires this attribute + +use compiletest_rs as compiletest; +use compiletest_rs::common::Mode as TestMode; + +use std::env::{self, set_var}; +use std::ffi::OsStr; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +mod cargo; + +fn host_lib() -> PathBuf { + if let Some(path) = option_env!("HOST_LIBS") { + PathBuf::from(path) + } else { + cargo::CARGO_TARGET_DIR.join(env!("PROFILE")) + } +} + +fn clippy_driver_path() -> PathBuf { + if let Some(path) = option_env!("CLIPPY_DRIVER_PATH") { + PathBuf::from(path) + } else { + cargo::TARGET_LIB.join("clippy-driver") + } +} + +// When we'll want to use `extern crate ..` for a dependency that is used +// both by the crate and the compiler itself, we can't simply pass -L flags +// as we'll get a duplicate matching versions. Instead, disambiguate with +// `--extern dep=path`. +// See https://github.com/rust-lang/rust-clippy/issues/4015. +// +// FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files. +// Because it would force-rebuild if the options passed to `build` command is not the same +// as what we manually pass to `cargo` invocation +fn third_party_crates() -> String { + use std::collections::HashMap; + static CRATES: &[&str] = &["serde", "serde_derive", "regex", "clippy_lints"]; + let dep_dir = cargo::TARGET_LIB.join("deps"); + let mut crates: HashMap<&str, PathBuf> = HashMap::with_capacity(CRATES.len()); + for entry in fs::read_dir(dep_dir).unwrap() { + let path = match entry { + Ok(entry) => entry.path(), + _ => continue, + }; + if let Some(name) = path.file_name().and_then(OsStr::to_str) { + for dep in CRATES { + if name.starts_with(&format!("lib{}-", dep)) && name.ends_with(".rlib") { + crates.entry(dep).or_insert(path); + break; + } + } + } + } + + let v: Vec<_> = crates + .into_iter() + .map(|(dep, path)| format!("--extern {}={}", dep, path.display())) + .collect(); + v.join(" ") +} + +fn default_config() -> compiletest::Config { + let mut config = compiletest::Config::default(); + + if let Ok(name) = env::var("TESTNAME") { + config.filter = Some(name); + } + + if let Some(path) = option_env!("RUSTC_LIB_PATH") { + let path = PathBuf::from(path); + config.run_lib_path = path.clone(); + config.compile_lib_path = path; + } + + config.target_rustcflags = Some(format!( + "-L {0} -L {1} -Dwarnings -Zui-testing {2}", + host_lib().join("deps").display(), + cargo::TARGET_LIB.join("deps").display(), + third_party_crates(), + )); + + config.build_base = if cargo::is_rustc_test_suite() { + // This make the stderr files go to clippy OUT_DIR on rustc repo build dir + let mut path = PathBuf::from(env!("OUT_DIR")); + path.push("test_build_base"); + path + } else { + host_lib().join("test_build_base") + }; + config.rustc_path = clippy_driver_path(); + config +} + +fn run_mode(cfg: &mut compiletest::Config) { + cfg.mode = TestMode::Ui; + cfg.src_base = Path::new("tests").join("ui"); + compiletest::run_tests(&cfg); +} + +#[allow(clippy::identity_conversion)] +fn run_ui_toml_tests(config: &compiletest::Config, mut tests: Vec) -> Result { + let mut result = true; + let opts = compiletest::test_opts(config); + for dir in fs::read_dir(&config.src_base)? { + let dir = dir?; + if !dir.file_type()?.is_dir() { + continue; + } + let dir_path = dir.path(); + set_var("CARGO_MANIFEST_DIR", &dir_path); + for file in fs::read_dir(&dir_path)? { + let file = file?; + let file_path = file.path(); + if file.file_type()?.is_dir() { + continue; + } + if file_path.extension() != Some(OsStr::new("rs")) { + continue; + } + let paths = compiletest::common::TestPaths { + file: file_path, + base: config.src_base.clone(), + relative_dir: dir_path.file_name().unwrap().into(), + }; + let test_name = compiletest::make_test_name(&config, &paths); + let index = tests + .iter() + .position(|test| test.desc.name == test_name) + .expect("The test should be in there"); + result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?; + } + } + Ok(result) +} + +fn run_ui_toml(config: &mut compiletest::Config) { + config.mode = TestMode::Ui; + config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap(); + + let tests = compiletest::make_tests(&config); + + let res = run_ui_toml_tests(&config, tests); + match res { + Ok(true) => {}, + Ok(false) => panic!("Some tests failed"), + Err(e) => { + println!("I/O failure during tests: {:?}", e); + }, + } +} + +fn prepare_env() { + set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); + set_var("CLIPPY_TESTS", "true"); + //set_var("RUST_BACKTRACE", "0"); +} + +#[test] +fn compile_test() { + prepare_env(); + let mut config = default_config(); + run_mode(&mut config); + run_ui_toml(&mut config); +} diff --git a/src/tools/clippy/tests/dogfood.rs b/src/tools/clippy/tests/dogfood.rs new file mode 100644 index 0000000000000..81af3d3033b23 --- /dev/null +++ b/src/tools/clippy/tests/dogfood.rs @@ -0,0 +1,76 @@ +// Dogfood cannot run on Windows +#![cfg(not(windows))] + +use lazy_static::lazy_static; +use std::path::PathBuf; +use std::process::Command; + +mod cargo; + +lazy_static! { + static ref CLIPPY_PATH: PathBuf = cargo::TARGET_LIB.join("cargo-clippy"); +} + +#[test] +fn dogfood_clippy() { + // run clippy on itself and fail the test if lint warnings are reported + if cargo::is_rustc_test_suite() { + return; + } + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let output = Command::new(&*CLIPPY_PATH) + .current_dir(root_dir) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy-preview") + .arg("--all-targets") + .arg("--all-features") + .arg("--") + .args(&["-D", "clippy::all"]) + .args(&["-D", "clippy::internal"]) + .args(&["-D", "clippy::pedantic"]) + .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); +} + +#[test] +fn dogfood_subprojects() { + // run clippy on remaining subprojects and fail the test if lint warnings are reported + if cargo::is_rustc_test_suite() { + return; + } + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + for d in &[ + "clippy_workspace_tests", + "clippy_workspace_tests/src", + "clippy_workspace_tests/subcrate", + "clippy_workspace_tests/subcrate/src", + "clippy_dev", + "rustc_tools_util", + ] { + let output = Command::new(&*CLIPPY_PATH) + .current_dir(root_dir.join(d)) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .arg("--") + .args(&["-D", "clippy::all"]) + .args(&["-D", "clippy::pedantic"]) + .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir + .output() + .unwrap(); + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); + } +} diff --git a/src/tools/clippy/tests/fmt.rs b/src/tools/clippy/tests/fmt.rs new file mode 100644 index 0000000000000..3aff8741f6051 --- /dev/null +++ b/src/tools/clippy/tests/fmt.rs @@ -0,0 +1,39 @@ +use std::path::PathBuf; +use std::process::Command; + +#[test] +fn fmt() { + if option_env!("RUSTC_TEST_SUITE").is_some() || option_env!("NO_FMT_TEST").is_some() { + return; + } + + // Skip this test if rustup nightly is unavailable + let rustup_output = Command::new("rustup") + .args(&["component", "list", "--toolchain", "nightly"]) + .output() + .unwrap(); + assert!(rustup_output.status.success()); + let component_output = String::from_utf8_lossy(&rustup_output.stdout); + if !component_output.contains("rustfmt") { + return; + } + + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let dev_dir = root_dir.join("clippy_dev"); + let target_dir = root_dir.join("target"); + let target_dir = target_dir.to_str().unwrap(); + let output = Command::new("cargo") + .current_dir(dev_dir) + .args(&["+nightly", "run", "--target-dir", target_dir, "--", "fmt", "--check"]) + .output() + .unwrap(); + + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!( + output.status.success(), + "Formatting check failed. Run `cargo dev fmt` to update formatting." + ); +} diff --git a/src/tools/clippy/tests/integration.rs b/src/tools/clippy/tests/integration.rs new file mode 100644 index 0000000000000..a78273ce0da41 --- /dev/null +++ b/src/tools/clippy/tests/integration.rs @@ -0,0 +1,82 @@ +#![cfg(feature = "integration")] + +use std::env; +use std::ffi::OsStr; +use std::process::Command; + +#[cfg_attr(feature = "integration", test)] +fn integration_test() { + let repo_name = env::var("INTEGRATION").expect("`INTEGRATION` var not set"); + let repo_url = format!("https://github.com/{}", repo_name); + let crate_name = repo_name + .split('/') + .nth(1) + .expect("repo name should have format `/`"); + + let mut repo_dir = tempfile::tempdir().expect("couldn't create temp dir").into_path(); + repo_dir.push(crate_name); + + let st = Command::new("git") + .args(&[ + OsStr::new("clone"), + OsStr::new("--depth=1"), + OsStr::new(&repo_url), + OsStr::new(&repo_dir), + ]) + .status() + .expect("unable to run git"); + assert!(st.success()); + + let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = std::path::Path::new(&root_dir).join("target"); + let clippy_binary = target_dir.join(env!("PROFILE")).join("cargo-clippy"); + + let output = Command::new(clippy_binary) + .current_dir(repo_dir) + .env("RUST_BACKTRACE", "full") + .env("CARGO_TARGET_DIR", target_dir) + .args(&[ + "clippy", + "--all-targets", + "--all-features", + "--", + "--cap-lints", + "warn", + "-Wclippy::pedantic", + "-Wclippy::nursery", + ]) + .output() + .expect("unable to run clippy"); + + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("internal compiler error") { + let backtrace_start = stderr + .find("thread 'rustc' panicked at") + .expect("start of backtrace not found"); + let backtrace_end = stderr + .rfind("error: internal compiler error") + .expect("end of backtrace not found"); + + panic!( + "internal compiler error\nBacktrace:\n\n{}", + &stderr[backtrace_start..backtrace_end] + ); + } else if stderr.contains("query stack during panic") { + panic!("query stack during panic in the output"); + } else if stderr.contains("E0463") { + // Encountering E0463 (can't find crate for `x`) did _not_ cause the build to fail in the + // past. Even though it should have. That's why we explicitly panic here. + // See PR #3552 and issue #3523 for more background. + panic!("error: E0463"); + } else if stderr.contains("E0514") { + panic!("incompatible crate versions"); + } else if stderr.contains("failed to run `rustc` to learn about target-specific information") { + panic!("couldn't find librustc_driver, consider setting `LD_LIBRARY_PATH`"); + } + + match output.status.code() { + Some(0) => println!("Compilation successful"), + Some(code) => eprintln!("Compilation failed. Exit code: {}", code), + None => panic!("Process terminated by signal"), + } +} diff --git a/src/tools/clippy/tests/missing-test-files.rs b/src/tools/clippy/tests/missing-test-files.rs new file mode 100644 index 0000000000000..d87bb4be3c3f9 --- /dev/null +++ b/src/tools/clippy/tests/missing-test-files.rs @@ -0,0 +1,56 @@ +#![allow(clippy::assertions_on_constants)] + +use std::fs::{self, DirEntry}; +use std::path::Path; + +#[test] +fn test_missing_tests() { + let missing_files = explore_directory(Path::new("./tests")); + if !missing_files.is_empty() { + assert!( + false, + format!( + "Didn't see a test file for the following files:\n\n{}\n", + missing_files + .iter() + .map(|s| format!("\t{}", s)) + .collect::>() + .join("\n") + ) + ); + } +} + +/* +Test for missing files. + +Since rs files are alphabetically before stderr/stdout, we can sort by the full name +and iter in that order. If we've seen the file stem for the first time and it's not +a rust file, it means the rust file has to be missing. +*/ +fn explore_directory(dir: &Path) -> Vec { + let mut missing_files: Vec = Vec::new(); + let mut current_file = String::new(); + let mut files: Vec = fs::read_dir(dir).unwrap().filter_map(Result::ok).collect(); + files.sort_by_key(std::fs::DirEntry::path); + for entry in &files { + let path = entry.path(); + if path.is_dir() { + missing_files.extend(explore_directory(&path)); + } else { + let file_stem = path.file_stem().unwrap().to_str().unwrap().to_string(); + if let Some(ext) = path.extension() { + match ext.to_str().unwrap() { + "rs" => current_file = file_stem.clone(), + "stderr" | "stdout" => { + if file_stem != current_file { + missing_files.push(path.to_str().unwrap().to_string()); + } + }, + _ => continue, + }; + } + } + } + missing_files +} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml/clippy.toml b/src/tools/clippy/tests/ui-toml/bad_toml/clippy.toml new file mode 100644 index 0000000000000..823e01a33b961 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml/clippy.toml @@ -0,0 +1,2 @@ +fn this_is_obviously(not: a, toml: file) { +} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.rs b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.rs new file mode 100644 index 0000000000000..3b9458fc2840e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.rs @@ -0,0 +1,3 @@ +// error-pattern: error reading Clippy's configuration file + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.stderr b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.stderr new file mode 100644 index 0000000000000..28c1a568a632b --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml/conf_bad_toml.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: expected an equals, found an identifier at line 1 column 4 + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/bad_toml_type/clippy.toml b/src/tools/clippy/tests/ui-toml/bad_toml_type/clippy.toml new file mode 100644 index 0000000000000..168675394d7f4 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml_type/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = 42 diff --git a/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.rs b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.rs new file mode 100644 index 0000000000000..8a0062423ad16 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.rs @@ -0,0 +1,4 @@ +// error-pattern: error reading Clippy's configuration file: `blacklisted-names` is expected to be a +// `Vec < String >` but is a `integer` + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.stderr b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.stderr new file mode 100644 index 0000000000000..efd02bcbb6e28 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/bad_toml_type/conf_bad_type.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/conf_deprecated_key/clippy.toml b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/clippy.toml new file mode 100644 index 0000000000000..ac47b195042eb --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/clippy.toml @@ -0,0 +1,6 @@ +# that one is an error +cyclomatic-complexity-threshold = 42 + +# that one is white-listed +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs new file mode 100644 index 0000000000000..2577c1eef92bc --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs @@ -0,0 +1,4 @@ +// error-pattern: error reading Clippy's configuration file: found deprecated field +// `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr new file mode 100644 index 0000000000000..34267c0daf7c2 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/clippy.toml b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/clippy.toml new file mode 100644 index 0000000000000..022eec3e0e2bf --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/clippy.toml @@ -0,0 +1 @@ +max-fn-params-bools = 1 diff --git a/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.rs b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.rs new file mode 100644 index 0000000000000..42897b389edfc --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.rs @@ -0,0 +1,6 @@ +#![warn(clippy::fn_params_excessive_bools)] + +fn f(_: bool) {} +fn g(_: bool, _: bool) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.stderr b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.stderr new file mode 100644 index 0000000000000..d05adc3d36e33 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/fn_params_excessive_bools/test.stderr @@ -0,0 +1,11 @@ +error: more than 1 bools in function parameters + --> $DIR/test.rs:4:1 + | +LL | fn g(_: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::fn-params-excessive-bools` implied by `-D warnings` + = help: consider refactoring bools into two-variant enums + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/clippy.toml b/src/tools/clippy/tests/ui-toml/functions_maxlines/clippy.toml new file mode 100644 index 0000000000000..951dbb523d955 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/clippy.toml @@ -0,0 +1 @@ +too-many-lines-threshold = 1 diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/test.rs b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.rs new file mode 100644 index 0000000000000..a47677a1f3a2e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.rs @@ -0,0 +1,45 @@ +#![warn(clippy::too_many_lines)] + +// This function should be considered one line. +fn many_comments_but_one_line_of_code() { + /* println!("This is good."); */ + // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* println!("This is good."); + println!("This is good."); + println!("This is good."); */ + println!("This is good."); +} + +// This should be considered two and a fail. +fn too_many_lines() { + println!("This is bad."); + println!("This is bad."); +} + +// This should be considered one line. +#[rustfmt::skip] +fn comment_starts_after_code() { + let _ = 5; /* closing comment. */ /* + this line shouldn't be counted theoretically. + */ +} + +// This should be considered one line. +fn comment_after_code() { + let _ = 5; /* this line should get counted once. */ +} + +// This should fail since it is technically two lines. +#[rustfmt::skip] +fn comment_before_code() { + let _ = "test"; + /* This comment extends to the front of + the code but this line should still count. */ let _ = 5; +} + +// This should be considered one line. +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr new file mode 100644 index 0000000000000..4b77ac551e770 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/functions_maxlines/test.stderr @@ -0,0 +1,23 @@ +error: This function has a large number of lines. + --> $DIR/test.rs:18:1 + | +LL | / fn too_many_lines() { +LL | | println!("This is bad."); +LL | | println!("This is bad."); +LL | | } + | |_^ + | + = note: `-D clippy::too-many-lines` implied by `-D warnings` + +error: This function has a large number of lines. + --> $DIR/test.rs:38:1 + | +LL | / fn comment_before_code() { +LL | | let _ = "test"; +LL | | /* This comment extends to the front of +LL | | the code but this line should still count. */ let _ = 5; +LL | | } + | |_^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/clippy.toml b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/clippy.toml new file mode 100644 index 0000000000000..a1dd6b2f0819c --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/clippy.toml @@ -0,0 +1,3 @@ +# that one is white-listed +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs new file mode 100644 index 0000000000000..270b9c5c43c13 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs @@ -0,0 +1,3 @@ +// error-pattern: should give absolutely no error + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/struct_excessive_bools/clippy.toml b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/clippy.toml new file mode 100644 index 0000000000000..3912ab542777d --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/clippy.toml @@ -0,0 +1 @@ +max-struct-bools = 0 diff --git a/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.rs b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.rs new file mode 100644 index 0000000000000..242984680e163 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.rs @@ -0,0 +1,9 @@ +#![warn(clippy::struct_excessive_bools)] + +struct S { + a: bool, +} + +struct Foo {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.stderr b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.stderr new file mode 100644 index 0000000000000..65861d10d0fdb --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/struct_excessive_bools/test.stderr @@ -0,0 +1,13 @@ +error: more than 0 bools in a struct + --> $DIR/test.rs:3:1 + | +LL | / struct S { +LL | | a: bool, +LL | | } + | |_^ + | + = note: `-D clippy::struct-excessive-bools` implied by `-D warnings` + = help: consider using a state machine or refactoring bools into two-variant enums + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/toml_blacklist/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_blacklist/clippy.toml new file mode 100644 index 0000000000000..6abe5a3bbc273 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_blacklist/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = ["toto", "tata", "titi"] diff --git a/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.rs b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.rs new file mode 100644 index 0000000000000..cb35d0e8589d2 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.rs @@ -0,0 +1,20 @@ +#![allow(dead_code)] +#![allow(clippy::single_match)] +#![allow(unused_variables)] +#![warn(clippy::blacklisted_name)] + +fn test(toto: ()) {} + +fn main() { + let toto = 42; + let tata = 42; + let titi = 42; + + let tatab = 42; + let tatatataic = 42; + + match (42, Some(1337), Some(0)) { + (toto, Some(tata), titi @ Some(_)) => (), + _ => (), + } +} diff --git a/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.stderr b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.stderr new file mode 100644 index 0000000000000..84ba77851f77e --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_blacklist/conf_french_blacklisted_name.stderr @@ -0,0 +1,46 @@ +error: use of a blacklisted/placeholder name `toto` + --> $DIR/conf_french_blacklisted_name.rs:6:9 + | +LL | fn test(toto: ()) {} + | ^^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: use of a blacklisted/placeholder name `toto` + --> $DIR/conf_french_blacklisted_name.rs:9:9 + | +LL | let toto = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `tata` + --> $DIR/conf_french_blacklisted_name.rs:10:9 + | +LL | let tata = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `titi` + --> $DIR/conf_french_blacklisted_name.rs:11:9 + | +LL | let titi = 42; + | ^^^^ + +error: use of a blacklisted/placeholder name `toto` + --> $DIR/conf_french_blacklisted_name.rs:17:10 + | +LL | (toto, Some(tata), titi @ Some(_)) => (), + | ^^^^ + +error: use of a blacklisted/placeholder name `tata` + --> $DIR/conf_french_blacklisted_name.rs:17:21 + | +LL | (toto, Some(tata), titi @ Some(_)) => (), + | ^^^^ + +error: use of a blacklisted/placeholder name `titi` + --> $DIR/conf_french_blacklisted_name.rs:17:28 + | +LL | (toto, Some(tata), titi @ Some(_)) => (), + | ^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_trivially_copy/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/clippy.toml new file mode 100644 index 0000000000000..3b96f1fd000bb --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/clippy.toml @@ -0,0 +1 @@ +trivial-copy-size-limit = 2 diff --git a/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.rs b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.rs new file mode 100644 index 0000000000000..19019a2541631 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.rs @@ -0,0 +1,21 @@ +// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)" +// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)" + +#![deny(clippy::trivially_copy_pass_by_ref)] +#![allow(clippy::many_single_char_names)] + +#[derive(Copy, Clone)] +struct Foo(u8); + +#[derive(Copy, Clone)] +struct Bar(u32); + +fn good(a: &mut u32, b: u32, c: &Bar, d: &u32) {} + +fn bad(x: &u16, y: &Foo) {} + +fn main() { + let (mut a, b, c, d, x, y) = (0, 0, Bar(0), 0, 0, Foo(0)); + good(&mut a, b, &c, &d); + bad(&x, &y); +} diff --git a/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.stderr b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.stderr new file mode 100644 index 0000000000000..912761a8f009c --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_trivially_copy/test.stderr @@ -0,0 +1,20 @@ +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/test.rs:15:11 + | +LL | fn bad(x: &u16, y: &Foo) {} + | ^^^^ help: consider passing by value instead: `u16` + | +note: the lint level is defined here + --> $DIR/test.rs:4:9 + | +LL | #![deny(clippy::trivially_copy_pass_by_ref)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/test.rs:15:20 + | +LL | fn bad(x: &u16, y: &Foo) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/clippy.toml b/src/tools/clippy/tests/ui-toml/toml_unknown_key/clippy.toml new file mode 100644 index 0000000000000..554b87cc50be1 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/clippy.toml @@ -0,0 +1,6 @@ +# that one is an error +foobar = 42 + +# that one is white-listed +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs new file mode 100644 index 0000000000000..a47569f62a322 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs @@ -0,0 +1,3 @@ +// error-pattern: error reading Clippy's configuration file: unknown key `foobar` + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr new file mode 100644 index 0000000000000..18f5d994ba8aa --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -0,0 +1,4 @@ +error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-struct-bools`, `max-fn-params-bools`, `third-party` at line 5 column 1 + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-toml/update-all-references.sh b/src/tools/clippy/tests/ui-toml/update-all-references.sh new file mode 100755 index 0000000000000..7028b251ea030 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/update-all-references.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# +# A script to update the references for all tests. The idea is that +# you do a run, which will generate files in the build directory +# containing the (normalized) actual output of the compiler. You then +# run this script, which will copy those files over. If you find +# yourself manually editing a foo.stderr file, you're doing it wrong. +# +# See all `update-references.sh`, if you just want to update a single test. + +if [[ "$1" == "--help" || "$1" == "-h" ]]; then + echo "usage: $0" +fi + +BUILD_DIR=$PWD/target/debug/test_build_base +MY_DIR=$(dirname "$0") +cd "$MY_DIR" || exit +find . -name '*.rs' -exec ./update-references.sh "$BUILD_DIR" {} + diff --git a/src/tools/clippy/tests/ui-toml/update-references.sh b/src/tools/clippy/tests/ui-toml/update-references.sh new file mode 100755 index 0000000000000..50d42678734e9 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/update-references.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# A script to update the references for particular tests. The idea is +# that you do a run, which will generate files in the build directory +# containing the (normalized) actual output of the compiler. This +# script will then copy that output and replace the "expected output" +# files. You can then commit the changes. +# +# If you find yourself manually editing a foo.stderr file, you're +# doing it wrong. + +if [[ "$1" == "--help" || "$1" == "-h" || "$1" == "" || "$2" == "" ]]; then + echo "usage: $0 " + echo "" + echo "For example:" + echo " $0 ../../../build/x86_64-apple-darwin/test/ui *.rs */*.rs" +fi + +MYDIR=$(dirname "$0") + +BUILD_DIR="$1" +shift + +while [[ "$1" != "" ]]; do + STDERR_NAME="${1/%.rs/.stderr}" + STDOUT_NAME="${1/%.rs/.stdout}" + shift + if [[ -f "$BUILD_DIR"/"$STDOUT_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$STDOUT_NAME" "$MYDIR"/"$STDOUT_NAME"); then + echo updating "$MYDIR"/"$STDOUT_NAME" + cp "$BUILD_DIR"/"$STDOUT_NAME" "$MYDIR"/"$STDOUT_NAME" + fi + if [[ -f "$BUILD_DIR"/"$STDERR_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$STDERR_NAME" "$MYDIR"/"$STDERR_NAME"); then + echo updating "$MYDIR"/"$STDERR_NAME" + cp "$BUILD_DIR"/"$STDERR_NAME" "$MYDIR"/"$STDERR_NAME" + fi +done diff --git a/src/tools/clippy/tests/ui-toml/vec_box_sized/clippy.toml b/src/tools/clippy/tests/ui-toml/vec_box_sized/clippy.toml new file mode 100644 index 0000000000000..039ea47fc32d8 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/vec_box_sized/clippy.toml @@ -0,0 +1 @@ +vec-box-size-threshold = 4 diff --git a/src/tools/clippy/tests/ui-toml/vec_box_sized/test.rs b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.rs new file mode 100644 index 0000000000000..bf04bee16373c --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.rs @@ -0,0 +1,15 @@ +struct S { + x: u64, +} + +struct C { + y: u16, +} + +struct Foo(Vec>); +struct Bar(Vec>); +struct Baz(Vec>); +struct BarBaz(Vec>); +struct FooBarBaz(Vec>); + +fn main() {} diff --git a/src/tools/clippy/tests/ui-toml/vec_box_sized/test.stderr b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.stderr new file mode 100644 index 0000000000000..3bdeca0bc8774 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/vec_box_sized/test.stderr @@ -0,0 +1,22 @@ +error: `Vec` is already on the heap, the boxing is unnecessary. + --> $DIR/test.rs:9:12 + | +LL | struct Foo(Vec>); + | ^^^^^^^^^^^^ help: try: `Vec` + | + = note: `-D clippy::vec-box` implied by `-D warnings` + +error: `Vec` is already on the heap, the boxing is unnecessary. + --> $DIR/test.rs:10:12 + | +LL | struct Bar(Vec>); + | ^^^^^^^^^^^^^ help: try: `Vec` + +error: `Vec` is already on the heap, the boxing is unnecessary. + --> $DIR/test.rs:13:18 + | +LL | struct FooBarBaz(Vec>); + | ^^^^^^^^^^^ help: try: `Vec` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/zero_single_char_names/clippy.toml b/src/tools/clippy/tests/ui-toml/zero_single_char_names/clippy.toml new file mode 100644 index 0000000000000..42a1067b95edd --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/zero_single_char_names/clippy.toml @@ -0,0 +1 @@ +single-char-binding-names-threshold = 0 diff --git a/src/tools/clippy/tests/ui-toml/zero_single_char_names/zero_single_char_names.rs b/src/tools/clippy/tests/ui-toml/zero_single_char_names/zero_single_char_names.rs new file mode 100644 index 0000000000000..22aaa242b9b9d --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/zero_single_char_names/zero_single_char_names.rs @@ -0,0 +1,3 @@ +#![warn(clippy::many_single_char_names)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/absurd-extreme-comparisons.rs b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.rs new file mode 100644 index 0000000000000..d205b383d1ff6 --- /dev/null +++ b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.rs @@ -0,0 +1,61 @@ +#![warn(clippy::absurd_extreme_comparisons)] +#![allow( + unused, + clippy::eq_op, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::needless_pass_by_value +)] + +#[rustfmt::skip] +fn main() { + const Z: u32 = 0; + let u: u32 = 42; + u <= 0; + u <= Z; + u < Z; + Z >= u; + Z > u; + u > u32::MAX; + u >= u32::MAX; + u32::MAX < u; + u32::MAX <= u; + 1-1 > u; + u >= !0; + u <= 12 - 2*6; + let i: i8 = 0; + i < -127 - 1; + i8::MAX >= i; + 3-7 < i32::MIN; + let b = false; + b >= true; + false > b; + u > 0; // ok + // this is handled by clippy::unit_cmp + () < {}; +} + +use std::cmp::{Ordering, PartialEq, PartialOrd}; + +#[derive(PartialEq, PartialOrd)] +pub struct U(u64); + +impl PartialEq for U { + fn eq(&self, other: &u32) -> bool { + self.eq(&U(u64::from(*other))) + } +} +impl PartialOrd for U { + fn partial_cmp(&self, other: &u32) -> Option { + self.partial_cmp(&U(u64::from(*other))) + } +} + +pub fn foo(val: U) -> bool { + val > u32::MAX +} + +pub fn bar(len: u64) -> bool { + // This is OK as we are casting from target sized to fixed size + len >= usize::MAX as u64 +} diff --git a/src/tools/clippy/tests/ui/absurd-extreme-comparisons.stderr b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.stderr new file mode 100644 index 0000000000000..6de554378aaa9 --- /dev/null +++ b/src/tools/clippy/tests/ui/absurd-extreme-comparisons.stderr @@ -0,0 +1,147 @@ +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:14:5 + | +LL | u <= 0; + | ^^^^^^ + | + = note: `-D clippy::absurd-extreme-comparisons` implied by `-D warnings` + = help: because `0` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `u == 0` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:15:5 + | +LL | u <= Z; + | ^^^^^^ + | + = help: because `Z` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `u == Z` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:16:5 + | +LL | u < Z; + | ^^^^^ + | + = help: because `Z` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:17:5 + | +LL | Z >= u; + | ^^^^^^ + | + = help: because `Z` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `Z == u` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:18:5 + | +LL | Z > u; + | ^^^^^ + | + = help: because `Z` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:19:5 + | +LL | u > u32::MAX; + | ^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:20:5 + | +LL | u >= u32::MAX; + | ^^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `u == u32::MAX` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:21:5 + | +LL | u32::MAX < u; + | ^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:22:5 + | +LL | u32::MAX <= u; + | ^^^^^^^^^^^^^ + | + = help: because `u32::MAX` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `u32::MAX == u` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:23:5 + | +LL | 1-1 > u; + | ^^^^^^^ + | + = help: because `1-1` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:24:5 + | +LL | u >= !0; + | ^^^^^^^ + | + = help: because `!0` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `u == !0` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:25:5 + | +LL | u <= 12 - 2*6; + | ^^^^^^^^^^^^^ + | + = help: because `12 - 2*6` is the minimum value for this type, the case where the two sides are not equal never occurs, consider using `u == 12 - 2*6` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:27:5 + | +LL | i < -127 - 1; + | ^^^^^^^^^^^^ + | + = help: because `-127 - 1` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:28:5 + | +LL | i8::MAX >= i; + | ^^^^^^^^^^^^ + | + = help: because `i8::MAX` is the maximum value for this type, this comparison is always true + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:29:5 + | +LL | 3-7 < i32::MIN; + | ^^^^^^^^^^^^^^ + | + = help: because `i32::MIN` is the minimum value for this type, this comparison is always false + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:31:5 + | +LL | b >= true; + | ^^^^^^^^^ + | + = help: because `true` is the maximum value for this type, the case where the two sides are not equal never occurs, consider using `b == true` instead + +error: this comparison involving the minimum or maximum element for this type contains a case that is always true or always false + --> $DIR/absurd-extreme-comparisons.rs:32:5 + | +LL | false > b; + | ^^^^^^^^^ + | + = help: because `false` is the minimum value for this type, this comparison is always false + +error: <-comparison of unit values detected. This will always be false + --> $DIR/absurd-extreme-comparisons.rs:35:5 + | +LL | () < {}; + | ^^^^^^^ + | + = note: `#[deny(clippy::unit_cmp)]` on by default + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/approx_const.rs b/src/tools/clippy/tests/ui/approx_const.rs new file mode 100644 index 0000000000000..fb57a0becbb25 --- /dev/null +++ b/src/tools/clippy/tests/ui/approx_const.rs @@ -0,0 +1,60 @@ +#[warn(clippy::approx_constant)] +#[allow(unused, clippy::shadow_unrelated, clippy::similar_names)] +fn main() { + let my_e = 2.7182; + let almost_e = 2.718; + let no_e = 2.71; + + let my_1_frac_pi = 0.3183; + let no_1_frac_pi = 0.31; + + let my_frac_1_sqrt_2 = 0.70710678; + let almost_frac_1_sqrt_2 = 0.70711; + let my_frac_1_sqrt_2 = 0.707; + + let my_frac_2_pi = 0.63661977; + let no_frac_2_pi = 0.636; + + let my_frac_2_sq_pi = 1.128379; + let no_frac_2_sq_pi = 1.128; + + let my_frac_pi_2 = 1.57079632679; + let no_frac_pi_2 = 1.5705; + + let my_frac_pi_3 = 1.04719755119; + let no_frac_pi_3 = 1.047; + + let my_frac_pi_4 = 0.785398163397; + let no_frac_pi_4 = 0.785; + + let my_frac_pi_6 = 0.523598775598; + let no_frac_pi_6 = 0.523; + + let my_frac_pi_8 = 0.3926990816987; + let no_frac_pi_8 = 0.392; + + let my_ln_10 = 2.302585092994046; + let no_ln_10 = 2.303; + + let my_ln_2 = 0.6931471805599453; + let no_ln_2 = 0.693; + + let my_log10_e = 0.4342944819032518; + let no_log10_e = 0.434; + + let my_log2_e = 1.4426950408889634; + let no_log2_e = 1.442; + + let log2_10 = 3.321928094887362; + let no_log2_10 = 3.321; + + let log10_2 = 0.301029995663981; + let no_log10_2 = 0.301; + + let my_pi = 3.1415; + let almost_pi = 3.14; + let no_pi = 3.15; + + let my_sq2 = 1.4142; + let no_sq2 = 1.414; +} diff --git a/src/tools/clippy/tests/ui/approx_const.stderr b/src/tools/clippy/tests/ui/approx_const.stderr new file mode 100644 index 0000000000000..98b85443f0b70 --- /dev/null +++ b/src/tools/clippy/tests/ui/approx_const.stderr @@ -0,0 +1,130 @@ +error: approximate value of `f{32, 64}::consts::E` found. Consider using it directly + --> $DIR/approx_const.rs:4:16 + | +LL | let my_e = 2.7182; + | ^^^^^^ + | + = note: `-D clippy::approx-constant` implied by `-D warnings` + +error: approximate value of `f{32, 64}::consts::E` found. Consider using it directly + --> $DIR/approx_const.rs:5:20 + | +LL | let almost_e = 2.718; + | ^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_1_PI` found. Consider using it directly + --> $DIR/approx_const.rs:8:24 + | +LL | let my_1_frac_pi = 0.3183; + | ^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found. Consider using it directly + --> $DIR/approx_const.rs:11:28 + | +LL | let my_frac_1_sqrt_2 = 0.70710678; + | ^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found. Consider using it directly + --> $DIR/approx_const.rs:12:32 + | +LL | let almost_frac_1_sqrt_2 = 0.70711; + | ^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_2_PI` found. Consider using it directly + --> $DIR/approx_const.rs:15:24 + | +LL | let my_frac_2_pi = 0.63661977; + | ^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_2_SQRT_PI` found. Consider using it directly + --> $DIR/approx_const.rs:18:27 + | +LL | let my_frac_2_sq_pi = 1.128379; + | ^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_2` found. Consider using it directly + --> $DIR/approx_const.rs:21:24 + | +LL | let my_frac_pi_2 = 1.57079632679; + | ^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_3` found. Consider using it directly + --> $DIR/approx_const.rs:24:24 + | +LL | let my_frac_pi_3 = 1.04719755119; + | ^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_4` found. Consider using it directly + --> $DIR/approx_const.rs:27:24 + | +LL | let my_frac_pi_4 = 0.785398163397; + | ^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_6` found. Consider using it directly + --> $DIR/approx_const.rs:30:24 + | +LL | let my_frac_pi_6 = 0.523598775598; + | ^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::FRAC_PI_8` found. Consider using it directly + --> $DIR/approx_const.rs:33:24 + | +LL | let my_frac_pi_8 = 0.3926990816987; + | ^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LN_10` found. Consider using it directly + --> $DIR/approx_const.rs:36:20 + | +LL | let my_ln_10 = 2.302585092994046; + | ^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LN_2` found. Consider using it directly + --> $DIR/approx_const.rs:39:19 + | +LL | let my_ln_2 = 0.6931471805599453; + | ^^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG10_E` found. Consider using it directly + --> $DIR/approx_const.rs:42:22 + | +LL | let my_log10_e = 0.4342944819032518; + | ^^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG2_E` found. Consider using it directly + --> $DIR/approx_const.rs:45:21 + | +LL | let my_log2_e = 1.4426950408889634; + | ^^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG2_10` found. Consider using it directly + --> $DIR/approx_const.rs:48:19 + | +LL | let log2_10 = 3.321928094887362; + | ^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::LOG10_2` found. Consider using it directly + --> $DIR/approx_const.rs:51:19 + | +LL | let log10_2 = 0.301029995663981; + | ^^^^^^^^^^^^^^^^^ + +error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly + --> $DIR/approx_const.rs:54:17 + | +LL | let my_pi = 3.1415; + | ^^^^^^ + +error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly + --> $DIR/approx_const.rs:55:21 + | +LL | let almost_pi = 3.14; + | ^^^^ + +error: approximate value of `f{32, 64}::consts::SQRT_2` found. Consider using it directly + --> $DIR/approx_const.rs:58:18 + | +LL | let my_sq2 = 1.4142; + | ^^^^^^ + +error: aborting due to 21 previous errors + diff --git a/src/tools/clippy/tests/ui/as_conversions.rs b/src/tools/clippy/tests/ui/as_conversions.rs new file mode 100644 index 0000000000000..e01ba0c64df32 --- /dev/null +++ b/src/tools/clippy/tests/ui/as_conversions.rs @@ -0,0 +1,7 @@ +#[warn(clippy::as_conversions)] + +fn main() { + let i = 0u32 as u64; + + let j = &i as *const u64 as *mut u64; +} diff --git a/src/tools/clippy/tests/ui/as_conversions.stderr b/src/tools/clippy/tests/ui/as_conversions.stderr new file mode 100644 index 0000000000000..312d3a7460ebe --- /dev/null +++ b/src/tools/clippy/tests/ui/as_conversions.stderr @@ -0,0 +1,27 @@ +error: using a potentially dangerous silent `as` conversion + --> $DIR/as_conversions.rs:4:13 + | +LL | let i = 0u32 as u64; + | ^^^^^^^^^^^ + | + = note: `-D clippy::as-conversions` implied by `-D warnings` + = help: consider using a safe wrapper for this conversion + +error: using a potentially dangerous silent `as` conversion + --> $DIR/as_conversions.rs:6:13 + | +LL | let j = &i as *const u64 as *mut u64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a safe wrapper for this conversion + +error: using a potentially dangerous silent `as` conversion + --> $DIR/as_conversions.rs:6:13 + | +LL | let j = &i as *const u64 as *mut u64; + | ^^^^^^^^^^^^^^^^ + | + = help: consider using a safe wrapper for this conversion + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/assertions_on_constants.rs b/src/tools/clippy/tests/ui/assertions_on_constants.rs new file mode 100644 index 0000000000000..60d721c2f2049 --- /dev/null +++ b/src/tools/clippy/tests/ui/assertions_on_constants.rs @@ -0,0 +1,29 @@ +macro_rules! assert_const { + ($len:expr) => { + assert!($len > 0); + debug_assert!($len < 0); + }; +} + +fn main() { + assert!(true); + assert!(false); + assert!(true, "true message"); + assert!(false, "false message"); + + let msg = "panic message"; + assert!(false, msg.to_uppercase()); + + const B: bool = true; + assert!(B); + + const C: bool = false; + assert!(C); + assert!(C, "C message"); + + debug_assert!(true); + // Don't lint this, since there is no better way for expressing "Only panic in debug mode". + debug_assert!(false); // #3948 + assert_const!(3); + assert_const!(-1); +} diff --git a/src/tools/clippy/tests/ui/assertions_on_constants.stderr b/src/tools/clippy/tests/ui/assertions_on_constants.stderr new file mode 100644 index 0000000000000..8f09c8ce9d52a --- /dev/null +++ b/src/tools/clippy/tests/ui/assertions_on_constants.stderr @@ -0,0 +1,84 @@ +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:9:5 + | +LL | assert!(true); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::assertions-on-constants` implied by `-D warnings` + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false)` should probably be replaced + --> $DIR/assertions_on_constants.rs:10:5 + | +LL | assert!(false); + | ^^^^^^^^^^^^^^^ + | + = help: use `panic!()` or `unreachable!()` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:11:5 + | +LL | assert!(true, "true message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false, "false message")` should probably be replaced + --> $DIR/assertions_on_constants.rs:12:5 + | +LL | assert!(false, "false message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `panic!("false message")` or `unreachable!("false message")` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false, msg.to_uppercase())` should probably be replaced + --> $DIR/assertions_on_constants.rs:15:5 + | +LL | assert!(false, msg.to_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `panic!(msg.to_uppercase())` or `unreachable!(msg.to_uppercase())` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:18:5 + | +LL | assert!(B); + | ^^^^^^^^^^^ + | + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false)` should probably be replaced + --> $DIR/assertions_on_constants.rs:21:5 + | +LL | assert!(C); + | ^^^^^^^^^^^ + | + = help: use `panic!()` or `unreachable!()` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert!(false, "C message")` should probably be replaced + --> $DIR/assertions_on_constants.rs:22:5 + | +LL | assert!(C, "C message"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `panic!("C message")` or `unreachable!("C message")` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `debug_assert!(true)` will be optimized out by the compiler + --> $DIR/assertions_on_constants.rs:24:5 + | +LL | debug_assert!(true); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: remove it + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/assign_ops.fixed b/src/tools/clippy/tests/ui/assign_ops.fixed new file mode 100644 index 0000000000000..52b1b3afe16fe --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops.fixed @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(dead_code, unused_assignments)] +#[warn(clippy::assign_op_pattern)] +fn main() { + let mut a = 5; + a += 1; + a += 1; + a -= 1; + a *= 99; + a *= 42; + a /= 2; + a %= 5; + a &= 1; + a = 1 - a; + a = 5 / a; + a = 42 % a; + a = 6 << a; + let mut s = String::new(); + s += "bla"; +} diff --git a/src/tools/clippy/tests/ui/assign_ops.rs b/src/tools/clippy/tests/ui/assign_ops.rs new file mode 100644 index 0000000000000..527a46b2c2bb9 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops.rs @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(dead_code, unused_assignments)] +#[warn(clippy::assign_op_pattern)] +fn main() { + let mut a = 5; + a = a + 1; + a = 1 + a; + a = a - 1; + a = a * 99; + a = 42 * a; + a = a / 2; + a = a % 5; + a = a & 1; + a = 1 - a; + a = 5 / a; + a = 42 % a; + a = 6 << a; + let mut s = String::new(); + s = s + "bla"; +} diff --git a/src/tools/clippy/tests/ui/assign_ops.stderr b/src/tools/clippy/tests/ui/assign_ops.stderr new file mode 100644 index 0000000000000..3486bd8da4d07 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops.stderr @@ -0,0 +1,58 @@ +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:7:5 + | +LL | a = a + 1; + | ^^^^^^^^^ help: replace it with: `a += 1` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:8:5 + | +LL | a = 1 + a; + | ^^^^^^^^^ help: replace it with: `a += 1` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:9:5 + | +LL | a = a - 1; + | ^^^^^^^^^ help: replace it with: `a -= 1` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:10:5 + | +LL | a = a * 99; + | ^^^^^^^^^^ help: replace it with: `a *= 99` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:11:5 + | +LL | a = 42 * a; + | ^^^^^^^^^^ help: replace it with: `a *= 42` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:12:5 + | +LL | a = a / 2; + | ^^^^^^^^^ help: replace it with: `a /= 2` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:13:5 + | +LL | a = a % 5; + | ^^^^^^^^^ help: replace it with: `a %= 5` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:14:5 + | +LL | a = a & 1; + | ^^^^^^^^^ help: replace it with: `a &= 1` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:20:5 + | +LL | s = s + "bla"; + | ^^^^^^^^^^^^^ help: replace it with: `s += "bla"` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/assign_ops2.rs b/src/tools/clippy/tests/ui/assign_ops2.rs new file mode 100644 index 0000000000000..4703a8c777788 --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops2.rs @@ -0,0 +1,55 @@ +#[allow(unused_assignments)] +#[warn(clippy::misrefactored_assign_op, clippy::assign_op_pattern)] +fn main() { + let mut a = 5; + a += a + 1; + a += 1 + a; + a -= a - 1; + a *= a * 99; + a *= 42 * a; + a /= a / 2; + a %= a % 5; + a &= a & 1; + a *= a * a; + a = a * a * a; + a = a * 42 * a; + a = a * 2 + a; + a -= 1 - a; + a /= 5 / a; + a %= 42 % a; + a <<= 6 << a; +} + +// check that we don't lint on op assign impls, because that's just the way to impl them + +use std::ops::{Mul, MulAssign}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Wrap(i64); + +impl Mul for Wrap { + type Output = Self; + + fn mul(self, rhs: i64) -> Self { + Wrap(self.0 * rhs) + } +} + +impl MulAssign for Wrap { + fn mul_assign(&mut self, rhs: i64) { + *self = *self * rhs + } +} + +fn cow_add_assign() { + use std::borrow::Cow; + let mut buf = Cow::Owned(String::from("bar")); + let cows = Cow::Borrowed("foo"); + + // this can be linted + buf = buf + cows.clone(); + + // this should not as cow Add is not commutative + buf = cows + buf; + println!("{}", buf); +} diff --git a/src/tools/clippy/tests/ui/assign_ops2.stderr b/src/tools/clippy/tests/ui/assign_ops2.stderr new file mode 100644 index 0000000000000..70b15d18a568b --- /dev/null +++ b/src/tools/clippy/tests/ui/assign_ops2.stderr @@ -0,0 +1,146 @@ +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:5:5 + | +LL | a += a + 1; + | ^^^^^^^^^^ + | + = note: `-D clippy::misrefactored-assign-op` implied by `-D warnings` +help: Did you mean `a = a + 1` or `a = a + a + 1`? Consider replacing it with + | +LL | a += 1; + | ^^^^^^ +help: or + | +LL | a = a + a + 1; + | ^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:6:5 + | +LL | a += 1 + a; + | ^^^^^^^^^^ + | +help: Did you mean `a = a + 1` or `a = a + 1 + a`? Consider replacing it with + | +LL | a += 1; + | ^^^^^^ +help: or + | +LL | a = a + 1 + a; + | ^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:7:5 + | +LL | a -= a - 1; + | ^^^^^^^^^^ + | +help: Did you mean `a = a - 1` or `a = a - (a - 1)`? Consider replacing it with + | +LL | a -= 1; + | ^^^^^^ +help: or + | +LL | a = a - (a - 1); + | ^^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:8:5 + | +LL | a *= a * 99; + | ^^^^^^^^^^^ + | +help: Did you mean `a = a * 99` or `a = a * a * 99`? Consider replacing it with + | +LL | a *= 99; + | ^^^^^^^ +help: or + | +LL | a = a * a * 99; + | ^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:9:5 + | +LL | a *= 42 * a; + | ^^^^^^^^^^^ + | +help: Did you mean `a = a * 42` or `a = a * 42 * a`? Consider replacing it with + | +LL | a *= 42; + | ^^^^^^^ +help: or + | +LL | a = a * 42 * a; + | ^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:10:5 + | +LL | a /= a / 2; + | ^^^^^^^^^^ + | +help: Did you mean `a = a / 2` or `a = a / (a / 2)`? Consider replacing it with + | +LL | a /= 2; + | ^^^^^^ +help: or + | +LL | a = a / (a / 2); + | ^^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:11:5 + | +LL | a %= a % 5; + | ^^^^^^^^^^ + | +help: Did you mean `a = a % 5` or `a = a % (a % 5)`? Consider replacing it with + | +LL | a %= 5; + | ^^^^^^ +help: or + | +LL | a = a % (a % 5); + | ^^^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:12:5 + | +LL | a &= a & 1; + | ^^^^^^^^^^ + | +help: Did you mean `a = a & 1` or `a = a & a & 1`? Consider replacing it with + | +LL | a &= 1; + | ^^^^^^ +help: or + | +LL | a = a & a & 1; + | ^^^^^^^^^^^^^ + +error: variable appears on both sides of an assignment operation + --> $DIR/assign_ops2.rs:13:5 + | +LL | a *= a * a; + | ^^^^^^^^^^ + | +help: Did you mean `a = a * a` or `a = a * a * a`? Consider replacing it with + | +LL | a *= a; + | ^^^^^^ +help: or + | +LL | a = a * a * a; + | ^^^^^^^^^^^^^ + +error: manual implementation of an assign operation + --> $DIR/assign_ops2.rs:50:5 + | +LL | buf = buf + cows.clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `buf += cows.clone()` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_bool.rs b/src/tools/clippy/tests/ui/atomic_ordering_bool.rs new file mode 100644 index 0000000000000..cdbde79b19ebf --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_bool.rs @@ -0,0 +1,25 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicBool, Ordering}; + +fn main() { + let x = AtomicBool::new(true); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(false, Ordering::Release); + x.store(false, Ordering::SeqCst); + x.store(false, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(false, Ordering::Acquire); + x.store(false, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_bool.stderr b/src/tools/clippy/tests/ui/atomic_ordering_bool.stderr new file mode 100644 index 0000000000000..397b893aed964 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_bool.stderr @@ -0,0 +1,35 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:14:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:15:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:23:20 + | +LL | x.store(false, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_bool.rs:24:20 + | +LL | x.store(false, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_fence.rs b/src/tools/clippy/tests/ui/atomic_ordering_fence.rs new file mode 100644 index 0000000000000..5ee5182ca051a --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_fence.rs @@ -0,0 +1,20 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{compiler_fence, fence, Ordering}; + +fn main() { + // Allowed fence ordering modes + fence(Ordering::Acquire); + fence(Ordering::Release); + fence(Ordering::AcqRel); + fence(Ordering::SeqCst); + + // Disallowed fence ordering modes + fence(Ordering::Relaxed); + + compiler_fence(Ordering::Acquire); + compiler_fence(Ordering::Release); + compiler_fence(Ordering::AcqRel); + compiler_fence(Ordering::SeqCst); + compiler_fence(Ordering::Relaxed); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_fence.stderr b/src/tools/clippy/tests/ui/atomic_ordering_fence.stderr new file mode 100644 index 0000000000000..3ceff27d9ad5e --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_fence.stderr @@ -0,0 +1,19 @@ +error: memory fences cannot have `Relaxed` ordering + --> $DIR/atomic_ordering_fence.rs:13:11 + | +LL | fence(Ordering::Relaxed); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst` + +error: memory fences cannot have `Relaxed` ordering + --> $DIR/atomic_ordering_fence.rs:19:20 + | +LL | compiler_fence(Ordering::Relaxed); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_int.rs b/src/tools/clippy/tests/ui/atomic_ordering_int.rs new file mode 100644 index 0000000000000..40a00ba3de350 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_int.rs @@ -0,0 +1,86 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, Ordering}; + +fn main() { + // `AtomicI8` test cases + let x = AtomicI8::new(0); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicI16` test cases + let x = AtomicI16::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicI32` test cases + let x = AtomicI32::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicI64` test cases + let x = AtomicI64::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicIsize` test cases + let x = AtomicIsize::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_int.stderr b/src/tools/clippy/tests/ui/atomic_ordering_int.stderr new file mode 100644 index 0000000000000..bbaf234d3c9f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_int.stderr @@ -0,0 +1,163 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:15:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:16:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:24:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:25:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:33:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:34:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:39:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:40:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:48:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:49:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:54:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:55:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:63:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:64:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:69:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:70:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:78:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:79:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:84:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_int.rs:85:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_ptr.rs b/src/tools/clippy/tests/ui/atomic_ordering_ptr.rs new file mode 100644 index 0000000000000..ecbb05c7fbc39 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_ptr.rs @@ -0,0 +1,27 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicPtr, Ordering}; + +fn main() { + let ptr = &mut 5; + let other_ptr = &mut 10; + let x = AtomicPtr::new(ptr); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(other_ptr, Ordering::Release); + x.store(other_ptr, Ordering::SeqCst); + x.store(other_ptr, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(other_ptr, Ordering::Acquire); + x.store(other_ptr, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_ptr.stderr b/src/tools/clippy/tests/ui/atomic_ordering_ptr.stderr new file mode 100644 index 0000000000000..558ae55518d5a --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_ptr.stderr @@ -0,0 +1,35 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:16:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:17:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:25:24 + | +LL | x.store(other_ptr, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_ptr.rs:26:24 + | +LL | x.store(other_ptr, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/atomic_ordering_uint.rs b/src/tools/clippy/tests/ui/atomic_ordering_uint.rs new file mode 100644 index 0000000000000..a0d5d7c401035 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_uint.rs @@ -0,0 +1,86 @@ +#![warn(clippy::invalid_atomic_ordering)] + +use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, AtomicUsize, Ordering}; + +fn main() { + // `AtomicU8` test cases + let x = AtomicU8::new(0); + + // Allowed load ordering modes + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + + // Disallowed load ordering modes + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + // Allowed store ordering modes + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + + // Disallowed store ordering modes + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicU16` test cases + let x = AtomicU16::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicU32` test cases + let x = AtomicU32::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicU64` test cases + let x = AtomicU64::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); + + // `AtomicUsize` test cases + let x = AtomicUsize::new(0); + + let _ = x.load(Ordering::Acquire); + let _ = x.load(Ordering::SeqCst); + let _ = x.load(Ordering::Relaxed); + let _ = x.load(Ordering::Release); + let _ = x.load(Ordering::AcqRel); + + x.store(1, Ordering::Release); + x.store(1, Ordering::SeqCst); + x.store(1, Ordering::Relaxed); + x.store(1, Ordering::Acquire); + x.store(1, Ordering::AcqRel); +} diff --git a/src/tools/clippy/tests/ui/atomic_ordering_uint.stderr b/src/tools/clippy/tests/ui/atomic_ordering_uint.stderr new file mode 100644 index 0000000000000..5703135bcf1e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/atomic_ordering_uint.stderr @@ -0,0 +1,163 @@ +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:15:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-atomic-ordering` implied by `-D warnings` + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:16:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:24:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:25:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:33:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:34:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:39:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:40:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:48:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:49:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:54:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:55:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:63:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:64:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:69:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:70:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:78:20 + | +LL | let _ = x.load(Ordering::Release); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic loads cannot have `Release` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:79:20 + | +LL | let _ = x.load(Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:84:16 + | +LL | x.store(1, Ordering::Acquire); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: atomic stores cannot have `Acquire` and `AcqRel` ordering + --> $DIR/atomic_ordering_uint.rs:85:16 + | +LL | x.store(1, Ordering::AcqRel); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using ordering modes `Release`, `SeqCst` or `Relaxed` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/attrs.rs b/src/tools/clippy/tests/ui/attrs.rs new file mode 100644 index 0000000000000..91b65a43be77f --- /dev/null +++ b/src/tools/clippy/tests/ui/attrs.rs @@ -0,0 +1,43 @@ +#![warn(clippy::inline_always, clippy::deprecated_semver)] +#![allow(clippy::assertions_on_constants)] +#[inline(always)] +fn test_attr_lint() { + assert!(true) +} + +#[inline(always)] +fn false_positive_expr() { + unreachable!() +} + +#[inline(always)] +fn false_positive_stmt() { + unreachable!(); +} + +#[inline(always)] +fn empty_and_false_positive_stmt() { + unreachable!(); +} + +#[deprecated(since = "forever")] +pub const SOME_CONST: u8 = 42; + +#[deprecated(since = "1")] +pub const ANOTHER_CONST: u8 = 23; + +#[deprecated(since = "0.1.1")] +pub const YET_ANOTHER_CONST: u8 = 0; + +fn main() { + test_attr_lint(); + if false { + false_positive_expr() + } + if false { + false_positive_stmt() + } + if false { + empty_and_false_positive_stmt() + } +} diff --git a/src/tools/clippy/tests/ui/attrs.stderr b/src/tools/clippy/tests/ui/attrs.stderr new file mode 100644 index 0000000000000..39ddf6f226d95 --- /dev/null +++ b/src/tools/clippy/tests/ui/attrs.stderr @@ -0,0 +1,24 @@ +error: you have declared `#[inline(always)]` on `test_attr_lint`. This is usually a bad idea + --> $DIR/attrs.rs:3:1 + | +LL | #[inline(always)] + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::inline-always` implied by `-D warnings` + +error: the since field must contain a semver-compliant version + --> $DIR/attrs.rs:23:14 + | +LL | #[deprecated(since = "forever")] + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::deprecated-semver` implied by `-D warnings` + +error: the since field must contain a semver-compliant version + --> $DIR/attrs.rs:26:14 + | +LL | #[deprecated(since = "1")] + | ^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/author.rs b/src/tools/clippy/tests/ui/author.rs new file mode 100644 index 0000000000000..0a1be35689670 --- /dev/null +++ b/src/tools/clippy/tests/ui/author.rs @@ -0,0 +1,4 @@ +fn main() { + #[clippy::author] + let x: char = 0x45 as char; +} diff --git a/src/tools/clippy/tests/ui/author.stdout b/src/tools/clippy/tests/ui/author.stdout new file mode 100644 index 0000000000000..211d11c7c1aa3 --- /dev/null +++ b/src/tools/clippy/tests/ui/author.stdout @@ -0,0 +1,14 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Cast(ref expr, ref cast_ty) = init.kind; + if let TyKind::Path(ref qp) = cast_ty.kind; + if match_qpath(qp, &["char"]); + if let ExprKind::Lit(ref lit) = expr.kind; + if let LitKind::Int(69, _) = lit.node; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind; + if name.as_str() == "x"; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/blocks.rs b/src/tools/clippy/tests/ui/author/blocks.rs new file mode 100644 index 0000000000000..a8068436b70bf --- /dev/null +++ b/src/tools/clippy/tests/ui/author/blocks.rs @@ -0,0 +1,16 @@ +#![feature(stmt_expr_attributes)] +#![allow(redundant_semicolons, clippy::no_effect)] + +#[rustfmt::skip] +fn main() { + #[clippy::author] + { + ;;;; + } +} + +#[clippy::author] +fn foo() { + let x = 42i32; + -x; +} diff --git a/src/tools/clippy/tests/ui/author/blocks.stdout b/src/tools/clippy/tests/ui/author/blocks.stdout new file mode 100644 index 0000000000000..c8ef75e48fc22 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/blocks.stdout @@ -0,0 +1,13 @@ +if_chain! { + if let ExprKind::Block(ref block) = expr.kind; + if let Some(trailing_expr) = &block.expr; + if block.stmts.len() == 0; + then { + // report your lint here + } +} +if_chain! { + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/call.rs b/src/tools/clippy/tests/ui/author/call.rs new file mode 100644 index 0000000000000..e99c3c41dc4e1 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/call.rs @@ -0,0 +1,4 @@ +fn main() { + #[clippy::author] + let _ = ::std::cmp::min(3, 4); +} diff --git a/src/tools/clippy/tests/ui/author/call.stdout b/src/tools/clippy/tests/ui/author/call.stdout new file mode 100644 index 0000000000000..4dccf666631a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/call.stdout @@ -0,0 +1,16 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Call(ref func, ref args) = init.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["{{root}}", "std", "cmp", "min"]); + if args.len() == 2; + if let ExprKind::Lit(ref lit) = args[0].kind; + if let LitKind::Int(3, _) = lit.node; + if let ExprKind::Lit(ref lit1) = args[1].kind; + if let LitKind::Int(4, _) = lit1.node; + if let PatKind::Wild = local.pat.kind; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/for_loop.rs b/src/tools/clippy/tests/ui/author/for_loop.rs new file mode 100644 index 0000000000000..b3dec876535c5 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/for_loop.rs @@ -0,0 +1,8 @@ +#![feature(stmt_expr_attributes)] + +fn main() { + #[clippy::author] + for y in 0..10 { + let z = y; + } +} diff --git a/src/tools/clippy/tests/ui/author/for_loop.stdout b/src/tools/clippy/tests/ui/author/for_loop.stdout new file mode 100644 index 0000000000000..81ede955347d7 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/for_loop.stdout @@ -0,0 +1,62 @@ +if_chain! { + if let ExprKind::DropTemps(ref expr) = expr.kind; + if let ExprKind::Match(ref expr1, ref arms, MatchSource::ForLoopDesugar) = expr.kind; + if let ExprKind::Call(ref func, ref args) = expr1.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["{{root}}", "std", "iter", "IntoIterator", "into_iter"]); + if args.len() == 1; + if let ExprKind::Struct(ref path1, ref fields, None) = args[0].kind; + if match_qpath(path1, &["{{root}}", "std", "ops", "Range"]); + if fields.len() == 2; + // unimplemented: field checks + if arms.len() == 1; + if let ExprKind::Loop(ref body, ref label, LoopSource::ForLoop) = arms[0].body.kind; + if let Some(trailing_expr) = &body.expr; + if body.stmts.len() == 4; + if let StmtKind::Local(ref local) = body.stmts[0].kind; + if let PatKind::Binding(BindingAnnotation::Mutable, _, name, None) = local.pat.kind; + if name.as_str() == "__next"; + if let StmtKind::Expr(ref e, _) = body.stmts[1].kind + if let ExprKind::Match(ref expr2, ref arms1, MatchSource::ForLoopDesugar) = e.kind; + if let ExprKind::Call(ref func1, ref args1) = expr2.kind; + if let ExprKind::Path(ref path2) = func1.kind; + if match_qpath(path2, &["{{root}}", "std", "iter", "Iterator", "next"]); + if args1.len() == 1; + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, ref inner) = args1[0].kind; + if let ExprKind::Path(ref path3) = inner.kind; + if match_qpath(path3, &["iter"]); + if arms1.len() == 2; + if let ExprKind::Assign(ref target, ref value, ref _span) = arms1[0].body.kind; + if let ExprKind::Path(ref path4) = target.kind; + if match_qpath(path4, &["__next"]); + if let ExprKind::Path(ref path5) = value.kind; + if match_qpath(path5, &["val"]); + if let PatKind::TupleStruct(ref path6, ref fields1, None) = arms1[0].pat.kind; + if match_qpath(path6, &["{{root}}", "std", "option", "Option", "Some"]); + if fields1.len() == 1; + // unimplemented: field checks + if let ExprKind::Break(ref destination, None) = arms1[1].body.kind; + if let PatKind::Path(ref path7) = arms1[1].pat.kind; + if match_qpath(path7, &["{{root}}", "std", "option", "Option", "None"]); + if let StmtKind::Local(ref local1) = body.stmts[2].kind; + if let Some(ref init) = local1.init; + if let ExprKind::Path(ref path8) = init.kind; + if match_qpath(path8, &["__next"]); + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local1.pat.kind; + if name1.as_str() == "y"; + if let StmtKind::Expr(ref e1, _) = body.stmts[3].kind + if let ExprKind::Block(ref block) = e1.kind; + if let Some(trailing_expr1) = &block.expr; + if block.stmts.len() == 1; + if let StmtKind::Local(ref local2) = block.stmts[0].kind; + if let Some(ref init1) = local2.init; + if let ExprKind::Path(ref path9) = init1.kind; + if match_qpath(path9, &["y"]); + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name2, None) = local2.pat.kind; + if name2.as_str() == "z"; + if let PatKind::Binding(BindingAnnotation::Mutable, _, name3, None) = arms[0].pat.kind; + if name3.as_str() == "iter"; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/if.rs b/src/tools/clippy/tests/ui/author/if.rs new file mode 100644 index 0000000000000..2e9cb1466d0b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/if.rs @@ -0,0 +1,10 @@ +#[allow(clippy::all)] + +fn main() { + #[clippy::author] + let _ = if true { + 1 == 1; + } else { + 2 == 2; + }; +} diff --git a/src/tools/clippy/tests/ui/author/if.stdout b/src/tools/clippy/tests/ui/author/if.stdout new file mode 100644 index 0000000000000..c18d035953e53 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/if.stdout @@ -0,0 +1,31 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let Some((ref cond, ref then, Some(else_))) = higher::if_block(&init); + if let ExprKind::Block(ref block) = else_.kind; + if let Some(trailing_expr) = &block.expr; + if block.stmts.len() == 1; + if let StmtKind::Semi(ref e, _) = block.stmts[0].kind + if let ExprKind::Binary(ref op, ref left, ref right) = e.kind; + if BinOpKind::Eq == op.node; + if let ExprKind::Lit(ref lit) = left.kind; + if let LitKind::Int(2, _) = lit.node; + if let ExprKind::Lit(ref lit1) = right.kind; + if let LitKind::Int(2, _) = lit1.node; + if let ExprKind::Lit(ref lit2) = cond.kind; + if let LitKind::Bool(true) = lit2.node; + if let ExprKind::Block(ref block1) = then.kind; + if let Some(trailing_expr1) = &block1.expr; + if block1.stmts.len() == 1; + if let StmtKind::Semi(ref e1, _) = block1.stmts[0].kind + if let ExprKind::Binary(ref op1, ref left1, ref right1) = e1.kind; + if BinOpKind::Eq == op1.node; + if let ExprKind::Lit(ref lit3) = left1.kind; + if let LitKind::Int(1, _) = lit3.node; + if let ExprKind::Lit(ref lit4) = right1.kind; + if let LitKind::Int(1, _) = lit4.node; + if let PatKind::Wild = local.pat.kind; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/issue_3849.rs b/src/tools/clippy/tests/ui/author/issue_3849.rs new file mode 100644 index 0000000000000..bae4570e539a1 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/issue_3849.rs @@ -0,0 +1,14 @@ +#![allow(dead_code)] +#![allow(clippy::zero_ptr)] +#![allow(clippy::transmute_ptr_to_ref)] +#![allow(clippy::transmuting_null)] + +pub const ZPTR: *const usize = 0 as *const _; + +fn main() { + unsafe { + #[clippy::author] + let _: &i32 = std::mem::transmute(ZPTR); + let _: &i32 = std::mem::transmute(0 as *const i32); + } +} diff --git a/src/tools/clippy/tests/ui/author/issue_3849.stdout b/src/tools/clippy/tests/ui/author/issue_3849.stdout new file mode 100644 index 0000000000000..65f93f3cdc06b --- /dev/null +++ b/src/tools/clippy/tests/ui/author/issue_3849.stdout @@ -0,0 +1,14 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Call(ref func, ref args) = init.kind; + if let ExprKind::Path(ref path) = func.kind; + if match_qpath(path, &["std", "mem", "transmute"]); + if args.len() == 1; + if let ExprKind::Path(ref path1) = args[0].kind; + if match_qpath(path1, &["ZPTR"]); + if let PatKind::Wild = local.pat.kind; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/author/matches.rs b/src/tools/clippy/tests/ui/author/matches.rs new file mode 100644 index 0000000000000..674e07ec2d3da --- /dev/null +++ b/src/tools/clippy/tests/ui/author/matches.rs @@ -0,0 +1,13 @@ +#![allow(clippy::let_and_return)] + +fn main() { + #[clippy::author] + let a = match 42 { + 16 => 5, + 17 => { + let x = 3; + x + }, + _ => 1, + }; +} diff --git a/src/tools/clippy/tests/ui/author/matches.stdout b/src/tools/clippy/tests/ui/author/matches.stdout new file mode 100644 index 0000000000000..2e8f8227dca12 --- /dev/null +++ b/src/tools/clippy/tests/ui/author/matches.stdout @@ -0,0 +1,33 @@ +if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if let Some(ref init) = local.init; + if let ExprKind::Match(ref expr, ref arms, MatchSource::Normal) = init.kind; + if let ExprKind::Lit(ref lit) = expr.kind; + if let LitKind::Int(42, _) = lit.node; + if arms.len() == 3; + if let ExprKind::Lit(ref lit1) = arms[0].body.kind; + if let LitKind::Int(5, _) = lit1.node; + if let PatKind::Lit(ref lit_expr) = arms[0].pat.kind + if let ExprKind::Lit(ref lit2) = lit_expr.kind; + if let LitKind::Int(16, _) = lit2.node; + if let ExprKind::Block(ref block) = arms[1].body.kind; + if let Some(trailing_expr) = &block.expr; + if block.stmts.len() == 1; + if let StmtKind::Local(ref local1) = block.stmts[0].kind; + if let Some(ref init1) = local1.init; + if let ExprKind::Lit(ref lit3) = init1.kind; + if let LitKind::Int(3, _) = lit3.node; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local1.pat.kind; + if name.as_str() == "x"; + if let PatKind::Lit(ref lit_expr1) = arms[1].pat.kind + if let ExprKind::Lit(ref lit4) = lit_expr1.kind; + if let LitKind::Int(17, _) = lit4.node; + if let ExprKind::Lit(ref lit5) = arms[2].body.kind; + if let LitKind::Int(1, _) = lit5.node; + if let PatKind::Wild = arms[2].pat.kind; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local.pat.kind; + if name1.as_str() == "a"; + then { + // report your lint here + } +} diff --git a/src/tools/clippy/tests/ui/auxiliary/doc_unsafe_macros.rs b/src/tools/clippy/tests/ui/auxiliary/doc_unsafe_macros.rs new file mode 100644 index 0000000000000..869672d1eda5e --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/doc_unsafe_macros.rs @@ -0,0 +1,8 @@ +#[macro_export] +macro_rules! undocd_unsafe { + () => { + pub unsafe fn oy_vey() { + unimplemented!(); + } + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/implicit_hasher_macros.rs b/src/tools/clippy/tests/ui/auxiliary/implicit_hasher_macros.rs new file mode 100644 index 0000000000000..1eb77c531835a --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/implicit_hasher_macros.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! implicit_hasher_fn { + () => { + pub fn f(input: &HashMap) {} + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs new file mode 100644 index 0000000000000..0bbb9534928ef --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs @@ -0,0 +1,58 @@ +#![allow(dead_code)] + +//! Used to test that certain lints don't trigger in imported external macros + +#[macro_export] +macro_rules! foofoo { + () => { + loop {} + }; +} + +#[macro_export] +macro_rules! must_use_unit { + () => { + #[must_use] + fn foo() {} + }; +} + +#[macro_export] +macro_rules! try_err { + () => { + pub fn try_err_fn() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { + Err(err)? + } else { + Ok(2) + } + } + }; +} + +#[macro_export] +macro_rules! string_add { + () => { + let y = "".to_owned(); + let z = y + "..."; + }; +} + +#[macro_export] +macro_rules! take_external { + ($s:expr) => { + std::mem::replace($s, Default::default()) + }; +} + +#[macro_export] +macro_rules! option_env_unwrap_external { + ($env: expr) => { + option_env!($env).unwrap() + }; + ($env: expr, $message: expr) => { + option_env!($env).expect($message) + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/option_helpers.rs b/src/tools/clippy/tests/ui/auxiliary/option_helpers.rs new file mode 100644 index 0000000000000..ed11c41e21c1a --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/option_helpers.rs @@ -0,0 +1,51 @@ +#![allow(dead_code, unused_variables)] + +/// Utility macro to test linting behavior in `option_methods()` +/// The lints included in `option_methods()` should not lint if the call to map is partially +/// within a macro +#[macro_export] +macro_rules! opt_map { + ($opt:expr, $map:expr) => { + ($opt).map($map) + }; +} + +/// Struct to generate false positive for Iterator-based lints +#[derive(Copy, Clone)] +pub struct IteratorFalsePositives { + pub foo: u32, +} + +impl IteratorFalsePositives { + pub fn filter(self) -> IteratorFalsePositives { + self + } + + pub fn next(self) -> IteratorFalsePositives { + self + } + + pub fn find(self) -> Option { + Some(self.foo) + } + + pub fn position(self) -> Option { + Some(self.foo) + } + + pub fn rposition(self) -> Option { + Some(self.foo) + } + + pub fn nth(self, n: usize) -> Option { + Some(self.foo) + } + + pub fn skip(self, _: usize) -> IteratorFalsePositives { + self + } + + pub fn skip_while(self) -> IteratorFalsePositives { + self + } +} diff --git a/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs new file mode 100644 index 0000000000000..21bb5b01e02b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs @@ -0,0 +1,22 @@ +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![feature(repr128, proc_macro_hygiene, proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::{quote, TokenStream}; + +#[proc_macro_derive(DeriveSomething)] +pub fn derive(_: TokenStream) -> TokenStream { + // Shound not trigger `used_underscore_binding` + let _inside_derive = 1; + assert_eq!(_inside_derive, _inside_derive); + + let output = quote! { + // Should not trigger `useless_attribute` + #[allow(dead_code)] + extern crate rustc_middle; + }; + output +} diff --git a/src/tools/clippy/tests/ui/auxiliary/use_self_macro.rs b/src/tools/clippy/tests/ui/auxiliary/use_self_macro.rs new file mode 100644 index 0000000000000..a8a85b4baefb7 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/use_self_macro.rs @@ -0,0 +1,15 @@ +macro_rules! use_self { + ( + impl $ty:ident { + fn func(&$this:ident) { + [fields($($field:ident)*)] + } + } + ) => ( + impl $ty { + fn func(&$this) { + let $ty { $($field),* } = $this; + } + } + ) +} diff --git a/src/tools/clippy/tests/ui/auxiliary/wildcard_imports_helper.rs b/src/tools/clippy/tests/ui/auxiliary/wildcard_imports_helper.rs new file mode 100644 index 0000000000000..414477aedd783 --- /dev/null +++ b/src/tools/clippy/tests/ui/auxiliary/wildcard_imports_helper.rs @@ -0,0 +1,21 @@ +pub use crate::extern_exports::*; + +pub fn extern_foo() {} +pub fn extern_bar() {} + +pub struct ExternA; + +pub mod inner { + pub mod inner_for_self_import { + pub fn inner_extern_foo() {} + pub fn inner_extern_bar() {} + } +} + +mod extern_exports { + pub fn extern_exported() {} + pub struct ExternExportedStruct; + pub enum ExternExportedEnum { + A, + } +} diff --git a/src/tools/clippy/tests/ui/await_holding_lock.rs b/src/tools/clippy/tests/ui/await_holding_lock.rs new file mode 100644 index 0000000000000..5c1fdd83efb0d --- /dev/null +++ b/src/tools/clippy/tests/ui/await_holding_lock.rs @@ -0,0 +1,64 @@ +// edition:2018 +#![warn(clippy::await_holding_lock)] + +use std::sync::Mutex; + +async fn bad(x: &Mutex) -> u32 { + let guard = x.lock().unwrap(); + baz().await +} + +async fn good(x: &Mutex) -> u32 { + { + let guard = x.lock().unwrap(); + let y = *guard + 1; + } + baz().await; + let guard = x.lock().unwrap(); + 47 +} + +async fn baz() -> u32 { + 42 +} + +async fn also_bad(x: &Mutex) -> u32 { + let first = baz().await; + + let guard = x.lock().unwrap(); + + let second = baz().await; + + let third = baz().await; + + first + second + third +} + +async fn not_good(x: &Mutex) -> u32 { + let first = baz().await; + + let second = { + let guard = x.lock().unwrap(); + baz().await + }; + + let third = baz().await; + + first + second + third +} + +fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { + async move { + let guard = x.lock().unwrap(); + baz().await + } +} + +fn main() { + let m = Mutex::new(100); + good(&m); + bad(&m); + also_bad(&m); + not_good(&m); + block_bad(&m); +} diff --git a/src/tools/clippy/tests/ui/await_holding_lock.stderr b/src/tools/clippy/tests/ui/await_holding_lock.stderr new file mode 100644 index 0000000000000..8c47cb37d8c99 --- /dev/null +++ b/src/tools/clippy/tests/ui/await_holding_lock.stderr @@ -0,0 +1,63 @@ +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await. + --> $DIR/await_holding_lock.rs:7:9 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | + = note: `-D clippy::await-holding-lock` implied by `-D warnings` +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:7:5 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | } + | |_^ + +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await. + --> $DIR/await_holding_lock.rs:28:9 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:28:5 + | +LL | / let guard = x.lock().unwrap(); +LL | | +LL | | let second = baz().await; +LL | | +... | +LL | | first + second + third +LL | | } + | |_^ + +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await. + --> $DIR/await_holding_lock.rs:41:13 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:41:9 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | }; + | |_____^ + +error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await. + --> $DIR/await_holding_lock.rs:52:13 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | +note: these are all the await points this lock is held through + --> $DIR/await_holding_lock.rs:52:9 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | } + | |_____^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/bit_masks.rs b/src/tools/clippy/tests/ui/bit_masks.rs new file mode 100644 index 0000000000000..cfb493fb52afb --- /dev/null +++ b/src/tools/clippy/tests/ui/bit_masks.rs @@ -0,0 +1,63 @@ +const THREE_BITS: i64 = 7; +const EVEN_MORE_REDIRECTION: i64 = THREE_BITS; + +#[warn(clippy::bad_bit_mask)] +#[allow( + clippy::ineffective_bit_mask, + clippy::identity_op, + clippy::no_effect, + clippy::unnecessary_operation +)] +fn main() { + let x = 5; + + x & 0 == 0; + x & 1 == 1; //ok, distinguishes bit 0 + x & 1 == 0; //ok, compared with zero + x & 2 == 1; + x | 0 == 0; //ok, equals x == 0 (maybe warn?) + x | 1 == 3; //ok, equals x == 2 || x == 3 + x | 3 == 3; //ok, equals x <= 3 + x | 3 == 2; + + x & 1 > 1; + x & 2 > 1; // ok, distinguishes x & 2 == 2 from x & 2 == 0 + x & 2 < 1; // ok, distinguishes x & 2 == 2 from x & 2 == 0 + x | 1 > 1; // ok (if a bit silly), equals x > 1 + x | 2 > 1; + x | 2 <= 2; // ok (if a bit silly), equals x <= 2 + + x & 192 == 128; // ok, tests for bit 7 and not bit 6 + x & 0xffc0 == 0xfe80; // ok + + // this also now works with constants + x & THREE_BITS == 8; + x | EVEN_MORE_REDIRECTION < 7; + + 0 & x == 0; + 1 | x > 1; + + // and should now also match uncommon usage + 1 < 2 | x; + 2 == 3 | x; + 1 == x & 2; + + x | 1 > 2; // no error, because we allowed ineffective bit masks + ineffective(); +} + +#[warn(clippy::ineffective_bit_mask)] +#[allow(clippy::bad_bit_mask, clippy::no_effect, clippy::unnecessary_operation)] +fn ineffective() { + let x = 5; + + x | 1 > 3; + x | 1 < 4; + x | 1 <= 3; + x | 1 >= 8; + + x | 1 > 2; // not an error (yet), better written as x >= 2 + x | 1 >= 7; // not an error (yet), better written as x >= 6 + x | 3 > 4; // not an error (yet), better written as x >= 4 + x | 4 <= 19; +} diff --git a/src/tools/clippy/tests/ui/bit_masks.stderr b/src/tools/clippy/tests/ui/bit_masks.stderr new file mode 100644 index 0000000000000..dc5ad6dfbdff9 --- /dev/null +++ b/src/tools/clippy/tests/ui/bit_masks.stderr @@ -0,0 +1,110 @@ +error: &-masking with zero + --> $DIR/bit_masks.rs:14:5 + | +LL | x & 0 == 0; + | ^^^^^^^^^^ + | + = note: `-D clippy::bad-bit-mask` implied by `-D warnings` + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/bit_masks.rs:14:5 + | +LL | x & 0 == 0; + | ^^^^^ + | + = note: `#[deny(clippy::erasing_op)]` on by default + +error: incompatible bit mask: `_ & 2` can never be equal to `1` + --> $DIR/bit_masks.rs:17:5 + | +LL | x & 2 == 1; + | ^^^^^^^^^^ + +error: incompatible bit mask: `_ | 3` can never be equal to `2` + --> $DIR/bit_masks.rs:21:5 + | +LL | x | 3 == 2; + | ^^^^^^^^^^ + +error: incompatible bit mask: `_ & 1` will never be higher than `1` + --> $DIR/bit_masks.rs:23:5 + | +LL | x & 1 > 1; + | ^^^^^^^^^ + +error: incompatible bit mask: `_ | 2` will always be higher than `1` + --> $DIR/bit_masks.rs:27:5 + | +LL | x | 2 > 1; + | ^^^^^^^^^ + +error: incompatible bit mask: `_ & 7` can never be equal to `8` + --> $DIR/bit_masks.rs:34:5 + | +LL | x & THREE_BITS == 8; + | ^^^^^^^^^^^^^^^^^^^ + +error: incompatible bit mask: `_ | 7` will never be lower than `7` + --> $DIR/bit_masks.rs:35:5 + | +LL | x | EVEN_MORE_REDIRECTION < 7; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: &-masking with zero + --> $DIR/bit_masks.rs:37:5 + | +LL | 0 & x == 0; + | ^^^^^^^^^^ + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/bit_masks.rs:37:5 + | +LL | 0 & x == 0; + | ^^^^^ + +error: incompatible bit mask: `_ | 2` will always be higher than `1` + --> $DIR/bit_masks.rs:41:5 + | +LL | 1 < 2 | x; + | ^^^^^^^^^ + +error: incompatible bit mask: `_ | 3` can never be equal to `2` + --> $DIR/bit_masks.rs:42:5 + | +LL | 2 == 3 | x; + | ^^^^^^^^^^ + +error: incompatible bit mask: `_ & 2` can never be equal to `1` + --> $DIR/bit_masks.rs:43:5 + | +LL | 1 == x & 2; + | ^^^^^^^^^^ + +error: ineffective bit mask: `x | 1` compared to `3`, is the same as x compared directly + --> $DIR/bit_masks.rs:54:5 + | +LL | x | 1 > 3; + | ^^^^^^^^^ + | + = note: `-D clippy::ineffective-bit-mask` implied by `-D warnings` + +error: ineffective bit mask: `x | 1` compared to `4`, is the same as x compared directly + --> $DIR/bit_masks.rs:55:5 + | +LL | x | 1 < 4; + | ^^^^^^^^^ + +error: ineffective bit mask: `x | 1` compared to `3`, is the same as x compared directly + --> $DIR/bit_masks.rs:56:5 + | +LL | x | 1 <= 3; + | ^^^^^^^^^^ + +error: ineffective bit mask: `x | 1` compared to `8`, is the same as x compared directly + --> $DIR/bit_masks.rs:57:5 + | +LL | x | 1 >= 8; + | ^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/blacklisted_name.rs b/src/tools/clippy/tests/ui/blacklisted_name.rs new file mode 100644 index 0000000000000..ca9d8d16b787d --- /dev/null +++ b/src/tools/clippy/tests/ui/blacklisted_name.rs @@ -0,0 +1,40 @@ +#![allow( + dead_code, + clippy::similar_names, + clippy::single_match, + clippy::toplevel_ref_arg, + unused_mut, + unused_variables +)] +#![warn(clippy::blacklisted_name)] + +fn test(foo: ()) {} + +fn main() { + let foo = 42; + let bar = 42; + let baz = 42; + + let barb = 42; + let barbaric = 42; + + match (42, Some(1337), Some(0)) { + (foo, Some(bar), baz @ Some(_)) => (), + _ => (), + } +} + +fn issue_1647(mut foo: u8) { + let mut bar = 0; + if let Some(mut baz) = Some(42) {} +} + +fn issue_1647_ref() { + let ref bar = 0; + if let Some(ref baz) = Some(42) {} +} + +fn issue_1647_ref_mut() { + let ref mut bar = 0; + if let Some(ref mut baz) = Some(42) {} +} diff --git a/src/tools/clippy/tests/ui/blacklisted_name.stderr b/src/tools/clippy/tests/ui/blacklisted_name.stderr new file mode 100644 index 0000000000000..44123829fb0f6 --- /dev/null +++ b/src/tools/clippy/tests/ui/blacklisted_name.stderr @@ -0,0 +1,88 @@ +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:11:9 + | +LL | fn test(foo: ()) {} + | ^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:14:9 + | +LL | let foo = 42; + | ^^^ + +error: use of a blacklisted/placeholder name `bar` + --> $DIR/blacklisted_name.rs:15:9 + | +LL | let bar = 42; + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:16:9 + | +LL | let baz = 42; + | ^^^ + +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:22:10 + | +LL | (foo, Some(bar), baz @ Some(_)) => (), + | ^^^ + +error: use of a blacklisted/placeholder name `bar` + --> $DIR/blacklisted_name.rs:22:20 + | +LL | (foo, Some(bar), baz @ Some(_)) => (), + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:22:26 + | +LL | (foo, Some(bar), baz @ Some(_)) => (), + | ^^^ + +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_name.rs:27:19 + | +LL | fn issue_1647(mut foo: u8) { + | ^^^ + +error: use of a blacklisted/placeholder name `bar` + --> $DIR/blacklisted_name.rs:28:13 + | +LL | let mut bar = 0; + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:29:21 + | +LL | if let Some(mut baz) = Some(42) {} + | ^^^ + +error: use of a blacklisted/placeholder name `bar` + --> $DIR/blacklisted_name.rs:33:13 + | +LL | let ref bar = 0; + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:34:21 + | +LL | if let Some(ref baz) = Some(42) {} + | ^^^ + +error: use of a blacklisted/placeholder name `bar` + --> $DIR/blacklisted_name.rs:38:17 + | +LL | let ref mut bar = 0; + | ^^^ + +error: use of a blacklisted/placeholder name `baz` + --> $DIR/blacklisted_name.rs:39:25 + | +LL | if let Some(ref mut baz) = Some(42) {} + | ^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/block_in_if_condition.fixed b/src/tools/clippy/tests/ui/block_in_if_condition.fixed new file mode 100644 index 0000000000000..955801e40f9b7 --- /dev/null +++ b/src/tools/clippy/tests/ui/block_in_if_condition.fixed @@ -0,0 +1,75 @@ +// run-rustfix +#![warn(clippy::block_in_if_condition_expr)] +#![warn(clippy::block_in_if_condition_stmt)] +#![allow(unused, clippy::let_and_return)] +#![warn(clippy::nonminimal_bool)] + +macro_rules! blocky { + () => {{ + true + }}; +} + +macro_rules! blocky_too { + () => {{ + let r = true; + r + }}; +} + +fn macro_if() { + if blocky!() {} + + if blocky_too!() {} +} + +fn condition_has_block() -> i32 { + let res = { + let x = 3; + x == 3 + }; if res { + 6 + } else { + 10 + } +} + +fn condition_has_block_with_single_expression() -> i32 { + if true { + 6 + } else { + 10 + } +} + +fn condition_is_normal() -> i32 { + let x = 3; + if x == 3 { + 6 + } else { + 10 + } +} + +fn condition_is_unsafe_block() { + let a: i32 = 1; + + // this should not warn because the condition is an unsafe block + if unsafe { 1u32 == std::mem::transmute(a) } { + println!("1u32 == a"); + } +} + +fn block_in_assert() { + let opt = Some(42); + assert!(opt + .as_ref() + .and_then(|val| { + let mut v = val * 2; + v -= 1; + Some(v * 3) + }) + .is_some()); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/block_in_if_condition.rs b/src/tools/clippy/tests/ui/block_in_if_condition.rs new file mode 100644 index 0000000000000..a6ea01d5fc5f9 --- /dev/null +++ b/src/tools/clippy/tests/ui/block_in_if_condition.rs @@ -0,0 +1,75 @@ +// run-rustfix +#![warn(clippy::block_in_if_condition_expr)] +#![warn(clippy::block_in_if_condition_stmt)] +#![allow(unused, clippy::let_and_return)] +#![warn(clippy::nonminimal_bool)] + +macro_rules! blocky { + () => {{ + true + }}; +} + +macro_rules! blocky_too { + () => {{ + let r = true; + r + }}; +} + +fn macro_if() { + if blocky!() {} + + if blocky_too!() {} +} + +fn condition_has_block() -> i32 { + if { + let x = 3; + x == 3 + } { + 6 + } else { + 10 + } +} + +fn condition_has_block_with_single_expression() -> i32 { + if { true } { + 6 + } else { + 10 + } +} + +fn condition_is_normal() -> i32 { + let x = 3; + if true && x == 3 { + 6 + } else { + 10 + } +} + +fn condition_is_unsafe_block() { + let a: i32 = 1; + + // this should not warn because the condition is an unsafe block + if unsafe { 1u32 == std::mem::transmute(a) } { + println!("1u32 == a"); + } +} + +fn block_in_assert() { + let opt = Some(42); + assert!(opt + .as_ref() + .and_then(|val| { + let mut v = val * 2; + v -= 1; + Some(v * 3) + }) + .is_some()); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/block_in_if_condition.stderr b/src/tools/clippy/tests/ui/block_in_if_condition.stderr new file mode 100644 index 0000000000000..b0a0a276c8908 --- /dev/null +++ b/src/tools/clippy/tests/ui/block_in_if_condition.stderr @@ -0,0 +1,36 @@ +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/block_in_if_condition.rs:27:5 + | +LL | / if { +LL | | let x = 3; +LL | | x == 3 +LL | | } { + | |_____^ + | + = note: `-D clippy::block-in-if-condition-stmt` implied by `-D warnings` +help: try + | +LL | let res = { +LL | let x = 3; +LL | x == 3 +LL | }; if res { + | + +error: omit braces around single expression condition + --> $DIR/block_in_if_condition.rs:38:8 + | +LL | if { true } { + | ^^^^^^^^ help: try: `true` + | + = note: `-D clippy::block-in-if-condition-expr` implied by `-D warnings` + +error: this boolean expression can be simplified + --> $DIR/block_in_if_condition.rs:47:8 + | +LL | if true && x == 3 { + | ^^^^^^^^^^^^^^ help: try: `x == 3` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/block_in_if_condition_closure.rs b/src/tools/clippy/tests/ui/block_in_if_condition_closure.rs new file mode 100644 index 0000000000000..bac3eda5e7f37 --- /dev/null +++ b/src/tools/clippy/tests/ui/block_in_if_condition_closure.rs @@ -0,0 +1,48 @@ +#![warn(clippy::block_in_if_condition_expr)] +#![warn(clippy::block_in_if_condition_stmt)] +#![allow(unused, clippy::let_and_return)] + +fn predicate bool, T>(pfn: F, val: T) -> bool { + pfn(val) +} + +fn pred_test() { + let v = 3; + let sky = "blue"; + // This is a sneaky case, where the block isn't directly in the condition, + // but is actually nside a closure that the condition is using. + // The same principle applies -- add some extra expressions to make sure + // linter isn't confused by them. + if v == 3 + && sky == "blue" + && predicate( + |x| { + let target = 3; + x == target + }, + v, + ) + {} + + if predicate( + |x| { + let target = 3; + x == target + }, + v, + ) {} +} + +fn closure_without_block() { + if predicate(|x| x == 3, 6) {} +} + +fn macro_in_closure() { + let option = Some(true); + + if option.unwrap_or_else(|| unimplemented!()) { + unimplemented!() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/block_in_if_condition_closure.stderr b/src/tools/clippy/tests/ui/block_in_if_condition_closure.stderr new file mode 100644 index 0000000000000..86cd24fe76321 --- /dev/null +++ b/src/tools/clippy/tests/ui/block_in_if_condition_closure.stderr @@ -0,0 +1,24 @@ +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/block_in_if_condition_closure.rs:19:17 + | +LL | |x| { + | _________________^ +LL | | let target = 3; +LL | | x == target +LL | | }, + | |_____________^ + | + = note: `-D clippy::block-in-if-condition-stmt` implied by `-D warnings` + +error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` + --> $DIR/block_in_if_condition_closure.rs:28:13 + | +LL | |x| { + | _____________^ +LL | | let target = 3; +LL | | x == target +LL | | }, + | |_________^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/bool_comparison.fixed b/src/tools/clippy/tests/ui/bool_comparison.fixed new file mode 100644 index 0000000000000..9121176475938 --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_comparison.fixed @@ -0,0 +1,129 @@ +// run-rustfix + +#[warn(clippy::bool_comparison)] +fn main() { + let x = true; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if x { + "yes" + } else { + "no" + }; + if !x { + "yes" + } else { + "no" + }; + let y = true; + if !x & y { + "yes" + } else { + "no" + }; + if x & !y { + "yes" + } else { + "no" + }; +} + +#[allow(dead_code)] +fn issue3703() { + struct Foo; + impl PartialEq for Foo { + fn eq(&self, _: &bool) -> bool { + true + } + } + impl PartialEq for bool { + fn eq(&self, _: &Foo) -> bool { + true + } + } + impl PartialOrd for Foo { + fn partial_cmp(&self, _: &bool) -> Option { + None + } + } + impl PartialOrd for bool { + fn partial_cmp(&self, _: &Foo) -> Option { + None + } + } + + if Foo == true {} + if true == Foo {} + if Foo != true {} + if true != Foo {} + if Foo == false {} + if false == Foo {} + if Foo != false {} + if false != Foo {} + if Foo < false {} + if false < Foo {} +} + +#[allow(dead_code)] +fn issue4983() { + let a = true; + let b = false; + + if a != b {}; + if a != b {}; + if a == b {}; + if !a == !b {}; + + if b != a {}; + if b != a {}; + if b == a {}; + if !b == !a {}; +} diff --git a/src/tools/clippy/tests/ui/bool_comparison.rs b/src/tools/clippy/tests/ui/bool_comparison.rs new file mode 100644 index 0000000000000..01ee35859f0da --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_comparison.rs @@ -0,0 +1,129 @@ +// run-rustfix + +#[warn(clippy::bool_comparison)] +fn main() { + let x = true; + if x == true { + "yes" + } else { + "no" + }; + if x == false { + "yes" + } else { + "no" + }; + if true == x { + "yes" + } else { + "no" + }; + if false == x { + "yes" + } else { + "no" + }; + if x != true { + "yes" + } else { + "no" + }; + if x != false { + "yes" + } else { + "no" + }; + if true != x { + "yes" + } else { + "no" + }; + if false != x { + "yes" + } else { + "no" + }; + if x < true { + "yes" + } else { + "no" + }; + if false < x { + "yes" + } else { + "no" + }; + if x > false { + "yes" + } else { + "no" + }; + if true > x { + "yes" + } else { + "no" + }; + let y = true; + if x < y { + "yes" + } else { + "no" + }; + if x > y { + "yes" + } else { + "no" + }; +} + +#[allow(dead_code)] +fn issue3703() { + struct Foo; + impl PartialEq for Foo { + fn eq(&self, _: &bool) -> bool { + true + } + } + impl PartialEq for bool { + fn eq(&self, _: &Foo) -> bool { + true + } + } + impl PartialOrd for Foo { + fn partial_cmp(&self, _: &bool) -> Option { + None + } + } + impl PartialOrd for bool { + fn partial_cmp(&self, _: &Foo) -> Option { + None + } + } + + if Foo == true {} + if true == Foo {} + if Foo != true {} + if true != Foo {} + if Foo == false {} + if false == Foo {} + if Foo != false {} + if false != Foo {} + if Foo < false {} + if false < Foo {} +} + +#[allow(dead_code)] +fn issue4983() { + let a = true; + let b = false; + + if a == !b {}; + if !a == b {}; + if a == b {}; + if !a == !b {}; + + if b == !a {}; + if !b == a {}; + if b == a {}; + if !b == !a {}; +} diff --git a/src/tools/clippy/tests/ui/bool_comparison.stderr b/src/tools/clippy/tests/ui/bool_comparison.stderr new file mode 100644 index 0000000000000..eeb1f20ee894d --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_comparison.stderr @@ -0,0 +1,112 @@ +error: equality checks against true are unnecessary + --> $DIR/bool_comparison.rs:6:8 + | +LL | if x == true { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + | + = note: `-D clippy::bool-comparison` implied by `-D warnings` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:11:8 + | +LL | if x == false { + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: equality checks against true are unnecessary + --> $DIR/bool_comparison.rs:16:8 + | +LL | if true == x { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: equality checks against false can be replaced by a negation + --> $DIR/bool_comparison.rs:21:8 + | +LL | if false == x { + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: inequality checks against true can be replaced by a negation + --> $DIR/bool_comparison.rs:26:8 + | +LL | if x != true { + | ^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: inequality checks against false are unnecessary + --> $DIR/bool_comparison.rs:31:8 + | +LL | if x != false { + | ^^^^^^^^^^ help: try simplifying it as shown: `x` + +error: inequality checks against true can be replaced by a negation + --> $DIR/bool_comparison.rs:36:8 + | +LL | if true != x { + | ^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: inequality checks against false are unnecessary + --> $DIR/bool_comparison.rs:41:8 + | +LL | if false != x { + | ^^^^^^^^^^ help: try simplifying it as shown: `x` + +error: less than comparison against true can be replaced by a negation + --> $DIR/bool_comparison.rs:46:8 + | +LL | if x < true { + | ^^^^^^^^ help: try simplifying it as shown: `!x` + +error: greater than checks against false are unnecessary + --> $DIR/bool_comparison.rs:51:8 + | +LL | if false < x { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: greater than checks against false are unnecessary + --> $DIR/bool_comparison.rs:56:8 + | +LL | if x > false { + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: less than comparison against true can be replaced by a negation + --> $DIR/bool_comparison.rs:61:8 + | +LL | if true > x { + | ^^^^^^^^ help: try simplifying it as shown: `!x` + +error: order comparisons between booleans can be simplified + --> $DIR/bool_comparison.rs:67:8 + | +LL | if x < y { + | ^^^^^ help: try simplifying it as shown: `!x & y` + +error: order comparisons between booleans can be simplified + --> $DIR/bool_comparison.rs:72:8 + | +LL | if x > y { + | ^^^^^ help: try simplifying it as shown: `x & !y` + +error: This comparison might be written more concisely + --> $DIR/bool_comparison.rs:120:8 + | +LL | if a == !b {}; + | ^^^^^^^ help: try simplifying it as shown: `a != b` + +error: This comparison might be written more concisely + --> $DIR/bool_comparison.rs:121:8 + | +LL | if !a == b {}; + | ^^^^^^^ help: try simplifying it as shown: `a != b` + +error: This comparison might be written more concisely + --> $DIR/bool_comparison.rs:125:8 + | +LL | if b == !a {}; + | ^^^^^^^ help: try simplifying it as shown: `b != a` + +error: This comparison might be written more concisely + --> $DIR/bool_comparison.rs:126:8 + | +LL | if !b == a {}; + | ^^^^^^^ help: try simplifying it as shown: `b != a` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/borrow_box.rs b/src/tools/clippy/tests/ui/borrow_box.rs new file mode 100644 index 0000000000000..1901de46ca894 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_box.rs @@ -0,0 +1,99 @@ +#![deny(clippy::borrowed_box)] +#![allow(clippy::blacklisted_name)] +#![allow(unused_variables)] +#![allow(dead_code)] + +pub fn test1(foo: &mut Box) { + // Although this function could be changed to "&mut bool", + // avoiding the Box, mutable references to boxes are not + // flagged by this lint. + // + // This omission is intentional: By passing a mutable Box, + // the memory location of the pointed-to object could be + // modified. By passing a mutable reference, the contents + // could change, but not the location. + println!("{:?}", foo) +} + +pub fn test2() { + let foo: &Box; +} + +struct Test3<'a> { + foo: &'a Box, +} + +trait Test4 { + fn test4(a: &Box); +} + +impl<'a> Test4 for Test3<'a> { + fn test4(a: &Box) { + unimplemented!(); + } +} + +use std::any::Any; + +pub fn test5(foo: &mut Box) { + println!("{:?}", foo) +} + +pub fn test6() { + let foo: &Box; +} + +struct Test7<'a> { + foo: &'a Box, +} + +trait Test8 { + fn test8(a: &Box); +} + +impl<'a> Test8 for Test7<'a> { + fn test8(a: &Box) { + unimplemented!(); + } +} + +pub fn test9(foo: &mut Box) { + let _ = foo; +} + +pub fn test10() { + let foo: &Box; +} + +struct Test11<'a> { + foo: &'a Box, +} + +trait Test12 { + fn test4(a: &Box); +} + +impl<'a> Test12 for Test11<'a> { + fn test4(a: &Box) { + unimplemented!(); + } +} + +pub fn test13(boxed_slice: &mut Box<[i32]>) { + // Unconditionally replaces the box pointer. + // + // This cannot be accomplished if "&mut [i32]" is passed, + // and provides a test case where passing a reference to + // a Box is valid. + let mut data = vec![12]; + *boxed_slice = data.into_boxed_slice(); +} + +fn main() { + test1(&mut Box::new(false)); + test2(); + test5(&mut (Box::new(false) as Box)); + test6(); + test9(&mut (Box::new(false) as Box)); + test10(); +} diff --git a/src/tools/clippy/tests/ui/borrow_box.stderr b/src/tools/clippy/tests/ui/borrow_box.stderr new file mode 100644 index 0000000000000..b5db691f89f39 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_box.stderr @@ -0,0 +1,26 @@ +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:19:14 + | +LL | let foo: &Box; + | ^^^^^^^^^^ help: try: `&bool` + | +note: the lint level is defined here + --> $DIR/borrow_box.rs:1:9 + | +LL | #![deny(clippy::borrowed_box)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:23:10 + | +LL | foo: &'a Box, + | ^^^^^^^^^^^^^ help: try: `&'a bool` + +error: you seem to be trying to use `&Box`. Consider using just `&T` + --> $DIR/borrow_box.rs:27:17 + | +LL | fn test4(a: &Box); + | ^^^^^^^^^^ help: try: `&bool` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const.rs b/src/tools/clippy/tests/ui/borrow_interior_mutable_const.rs new file mode 100644 index 0000000000000..fef9f4f39f809 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const.rs @@ -0,0 +1,85 @@ +#![warn(clippy::borrow_interior_mutable_const)] +#![allow(clippy::declare_interior_mutable_const, clippy::ref_in_deref)] + +use std::borrow::Cow; +use std::cell::Cell; +use std::fmt::Display; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Once; + +const ATOMIC: AtomicUsize = AtomicUsize::new(5); +const CELL: Cell = Cell::new(6); +const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); +const INTEGER: u8 = 8; +const STRING: String = String::new(); +const STR: &str = "012345"; +const COW: Cow = Cow::Borrowed("abcdef"); +const NO_ANN: &dyn Display = &70; +static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); +const ONCE_INIT: Once = Once::new(); + +trait Trait: Copy { + type NonCopyType; + + const ATOMIC: AtomicUsize; +} + +impl Trait for u64 { + type NonCopyType = u16; + + const ATOMIC: AtomicUsize = AtomicUsize::new(9); +} + +fn main() { + ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability + assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability + + let _once = ONCE_INIT; + let _once_ref = &ONCE_INIT; //~ ERROR interior mutability + let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability + let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability + let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability + let _atomic_into_inner = ATOMIC.into_inner(); + // these should be all fine. + let _twice = (ONCE_INIT, ONCE_INIT); + let _ref_twice = &(ONCE_INIT, ONCE_INIT); + let _ref_once = &(ONCE_INIT, ONCE_INIT).0; + let _array_twice = [ONCE_INIT, ONCE_INIT]; + let _ref_array_twice = &[ONCE_INIT, ONCE_INIT]; + let _ref_array_once = &[ONCE_INIT, ONCE_INIT][0]; + + // referencing projection is still bad. + let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability + let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability + let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability + let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mutability + let _ = &*ATOMIC_TUPLE.1; //~ ERROR interior mutability + let _ = &ATOMIC_TUPLE.2; + let _ = (&&&&ATOMIC_TUPLE).0; + let _ = (&&&&ATOMIC_TUPLE).2; + let _ = ATOMIC_TUPLE.0; + let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + let _ = ATOMIC_TUPLE.1.into_iter(); + let _ = ATOMIC_TUPLE.2; + let _ = &{ ATOMIC_TUPLE }; + + CELL.set(2); //~ ERROR interior mutability + assert_eq!(CELL.get(), 6); //~ ERROR interior mutability + + assert_eq!(INTEGER, 8); + assert!(STRING.is_empty()); + + let a = ATOMIC; + a.store(4, Ordering::SeqCst); + assert_eq!(a.load(Ordering::SeqCst), 4); + + STATIC_TUPLE.0.store(3, Ordering::SeqCst); + assert_eq!(STATIC_TUPLE.0.load(Ordering::SeqCst), 3); + assert!(STATIC_TUPLE.1.is_empty()); + + u64::ATOMIC.store(5, Ordering::SeqCst); //~ ERROR interior mutability + assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability + + assert_eq!(NO_ANN.to_string(), "70"); // should never lint this. +} diff --git a/src/tools/clippy/tests/ui/borrow_interior_mutable_const.stderr b/src/tools/clippy/tests/ui/borrow_interior_mutable_const.stderr new file mode 100644 index 0000000000000..dc738064a1718 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_interior_mutable_const.stderr @@ -0,0 +1,131 @@ +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:34:5 + | +LL | ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability + | ^^^^^^ + | + = note: `-D clippy::borrow-interior-mutable-const` implied by `-D warnings` + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:35:16 + | +LL | assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability + | ^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:38:22 + | +LL | let _once_ref = &ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:39:25 + | +LL | let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:40:27 + | +LL | let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:41:26 + | +LL | let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability + | ^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:52:14 + | +LL | let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:53:14 + | +LL | let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:54:19 + | +LL | let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:55:14 + | +LL | let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:56:13 + | +LL | let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:62:13 + | +LL | let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability + | ^^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:67:5 + | +LL | CELL.set(2); //~ ERROR interior mutability + | ^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:68:16 + | +LL | assert_eq!(CELL.get(), 6); //~ ERROR interior mutability + | ^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:81:5 + | +LL | u64::ATOMIC.store(5, Ordering::SeqCst); //~ ERROR interior mutability + | ^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: a `const` item with interior mutability should not be borrowed + --> $DIR/borrow_interior_mutable_const.rs:82:16 + | +LL | assert_eq!(u64::ATOMIC.load(Ordering::SeqCst), 9); //~ ERROR interior mutability + | ^^^^^^^^^^^ + | + = help: assign this const to a local or static variable, and use the variable here + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/box_vec.rs b/src/tools/clippy/tests/ui/box_vec.rs new file mode 100644 index 0000000000000..87b67c23704c9 --- /dev/null +++ b/src/tools/clippy/tests/ui/box_vec.rs @@ -0,0 +1,32 @@ +#![warn(clippy::all)] +#![allow(clippy::boxed_local, clippy::needless_pass_by_value)] +#![allow(clippy::blacklisted_name)] + +macro_rules! boxit { + ($init:expr, $x:ty) => { + let _: Box<$x> = Box::new($init); + }; +} + +fn test_macro() { + boxit!(Vec::new(), Vec); +} +pub fn test(foo: Box>) { + println!("{:?}", foo.get(0)) +} + +pub fn test2(foo: Box)>) { + // pass if #31 is fixed + foo(vec![1, 2, 3]) +} + +pub fn test_local_not_linted() { + let _: Box>; +} + +fn main() { + test(Box::new(Vec::new())); + test2(Box::new(|v| println!("{:?}", v))); + test_macro(); + test_local_not_linted(); +} diff --git a/src/tools/clippy/tests/ui/box_vec.stderr b/src/tools/clippy/tests/ui/box_vec.stderr new file mode 100644 index 0000000000000..fca12eddd573f --- /dev/null +++ b/src/tools/clippy/tests/ui/box_vec.stderr @@ -0,0 +1,11 @@ +error: you seem to be trying to use `Box>`. Consider using just `Vec` + --> $DIR/box_vec.rs:14:18 + | +LL | pub fn test(foo: Box>) { + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::box-vec` implied by `-D warnings` + = help: `Vec` is already on the heap, `Box>` makes an extra allocation. + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/builtin-type-shadow.rs b/src/tools/clippy/tests/ui/builtin-type-shadow.rs new file mode 100644 index 0000000000000..69b8b6a0e68c3 --- /dev/null +++ b/src/tools/clippy/tests/ui/builtin-type-shadow.rs @@ -0,0 +1,9 @@ +#![warn(clippy::builtin_type_shadow)] +#![allow(non_camel_case_types)] + +fn foo(a: u32) -> u32 { + 42 + // ^ rustc's type error +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/builtin-type-shadow.stderr b/src/tools/clippy/tests/ui/builtin-type-shadow.stderr new file mode 100644 index 0000000000000..b6a4adde84884 --- /dev/null +++ b/src/tools/clippy/tests/ui/builtin-type-shadow.stderr @@ -0,0 +1,26 @@ +error: This generic shadows the built-in type `u32` + --> $DIR/builtin-type-shadow.rs:4:8 + | +LL | fn foo(a: u32) -> u32 { + | ^^^ + | + = note: `-D clippy::builtin-type-shadow` implied by `-D warnings` + +error[E0308]: mismatched types + --> $DIR/builtin-type-shadow.rs:5:5 + | +LL | fn foo(a: u32) -> u32 { + | --- --- expected `u32` because of return type + | | + | this type parameter +LL | 42 + | ^^ expected type parameter `u32`, found integer + | + = note: expected type parameter `u32` + found type `{integer}` + = help: type parameters must be constrained to match other types + = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/tools/clippy/tests/ui/bytecount.rs b/src/tools/clippy/tests/ui/bytecount.rs new file mode 100644 index 0000000000000..c724ee21be310 --- /dev/null +++ b/src/tools/clippy/tests/ui/bytecount.rs @@ -0,0 +1,24 @@ +#[deny(clippy::naive_bytecount)] +fn main() { + let x = vec![0_u8; 16]; + + let _ = x.iter().filter(|&&a| a == 0).count(); // naive byte count + + let _ = (&x[..]).iter().filter(|&a| *a == 0).count(); // naive byte count + + let _ = x.iter().filter(|a| **a > 0).count(); // not an equality count, OK. + + let _ = x.iter().map(|a| a + 1).filter(|&a| a < 15).count(); // not a slice + + let b = 0; + + let _ = x.iter().filter(|_| b > 0).count(); // woah there + + let _ = x.iter().filter(|_a| b == b + 1).count(); // nothing to see here, move along + + let _ = x.iter().filter(|a| b + 1 == **a).count(); // naive byte count + + let y = vec![0_u16; 3]; + + let _ = y.iter().filter(|&&a| a == 0).count(); // naive count, but not bytes +} diff --git a/src/tools/clippy/tests/ui/bytecount.stderr b/src/tools/clippy/tests/ui/bytecount.stderr new file mode 100644 index 0000000000000..436f5d86a0627 --- /dev/null +++ b/src/tools/clippy/tests/ui/bytecount.stderr @@ -0,0 +1,26 @@ +error: You appear to be counting bytes the naive way + --> $DIR/bytecount.rs:5:13 + | +LL | let _ = x.iter().filter(|&&a| a == 0).count(); // naive byte count + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider using the bytecount crate: `bytecount::count(x, 0)` + | +note: the lint level is defined here + --> $DIR/bytecount.rs:1:8 + | +LL | #[deny(clippy::naive_bytecount)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: You appear to be counting bytes the naive way + --> $DIR/bytecount.rs:7:13 + | +LL | let _ = (&x[..]).iter().filter(|&a| *a == 0).count(); // naive byte count + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider using the bytecount crate: `bytecount::count((&x[..]), 0)` + +error: You appear to be counting bytes the naive way + --> $DIR/bytecount.rs:19:13 + | +LL | let _ = x.iter().filter(|a| b + 1 == **a).count(); // naive byte count + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider using the bytecount crate: `bytecount::count(x, b + 1)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/cast.rs b/src/tools/clippy/tests/ui/cast.rs new file mode 100644 index 0000000000000..7e0b211d862ca --- /dev/null +++ b/src/tools/clippy/tests/ui/cast.rs @@ -0,0 +1,95 @@ +#[warn( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap +)] +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + // Test clippy::cast_precision_loss + let x0 = 1i32; + x0 as f32; + let x1 = 1i64; + x1 as f32; + x1 as f64; + let x2 = 1u32; + x2 as f32; + let x3 = 1u64; + x3 as f32; + x3 as f64; + // Test clippy::cast_possible_truncation + 1f32 as i32; + 1f32 as u32; + 1f64 as f32; + 1i32 as i8; + 1i32 as u8; + 1f64 as isize; + 1f64 as usize; + // Test clippy::cast_possible_wrap + 1u8 as i8; + 1u16 as i16; + 1u32 as i32; + 1u64 as i64; + 1usize as isize; + // Test clippy::cast_sign_loss + 1i32 as u32; + -1i32 as u32; + 1isize as usize; + -1isize as usize; + 0i8 as u8; + i8::max_value() as u8; + i16::max_value() as u16; + i32::max_value() as u32; + i64::max_value() as u64; + i128::max_value() as u128; + + (-1i8).abs() as u8; + (-1i16).abs() as u16; + (-1i32).abs() as u32; + (-1i64).abs() as u64; + (-1isize).abs() as usize; + + (-1i8).checked_abs().unwrap() as u8; + (-1i16).checked_abs().unwrap() as u16; + (-1i32).checked_abs().unwrap() as u32; + (-1i64).checked_abs().unwrap() as u64; + (-1isize).checked_abs().unwrap() as usize; + + (-1i8).rem_euclid(1i8) as u8; + (-1i8).rem_euclid(1i8) as u16; + (-1i16).rem_euclid(1i16) as u16; + (-1i16).rem_euclid(1i16) as u32; + (-1i32).rem_euclid(1i32) as u32; + (-1i32).rem_euclid(1i32) as u64; + (-1i64).rem_euclid(1i64) as u64; + (-1i64).rem_euclid(1i64) as u128; + (-1isize).rem_euclid(1isize) as usize; + (1i8).rem_euclid(-1i8) as u8; + (1i8).rem_euclid(-1i8) as u16; + (1i16).rem_euclid(-1i16) as u16; + (1i16).rem_euclid(-1i16) as u32; + (1i32).rem_euclid(-1i32) as u32; + (1i32).rem_euclid(-1i32) as u64; + (1i64).rem_euclid(-1i64) as u64; + (1i64).rem_euclid(-1i64) as u128; + (1isize).rem_euclid(-1isize) as usize; + + (-1i8).checked_rem_euclid(1i8).unwrap() as u8; + (-1i8).checked_rem_euclid(1i8).unwrap() as u16; + (-1i16).checked_rem_euclid(1i16).unwrap() as u16; + (-1i16).checked_rem_euclid(1i16).unwrap() as u32; + (-1i32).checked_rem_euclid(1i32).unwrap() as u32; + (-1i32).checked_rem_euclid(1i32).unwrap() as u64; + (-1i64).checked_rem_euclid(1i64).unwrap() as u64; + (-1i64).checked_rem_euclid(1i64).unwrap() as u128; + (-1isize).checked_rem_euclid(1isize).unwrap() as usize; + (1i8).checked_rem_euclid(-1i8).unwrap() as u8; + (1i8).checked_rem_euclid(-1i8).unwrap() as u16; + (1i16).checked_rem_euclid(-1i16).unwrap() as u16; + (1i16).checked_rem_euclid(-1i16).unwrap() as u32; + (1i32).checked_rem_euclid(-1i32).unwrap() as u32; + (1i32).checked_rem_euclid(-1i32).unwrap() as u64; + (1i64).checked_rem_euclid(-1i64).unwrap() as u64; + (1i64).checked_rem_euclid(-1i64).unwrap() as u128; + (1isize).checked_rem_euclid(-1isize).unwrap() as usize; +} diff --git a/src/tools/clippy/tests/ui/cast.stderr b/src/tools/clippy/tests/ui/cast.stderr new file mode 100644 index 0000000000000..4c66d73649484 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast.stderr @@ -0,0 +1,142 @@ +error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:11:5 + | +LL | x0 as f32; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-precision-loss` implied by `-D warnings` + +error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:13:5 + | +LL | x1 as f32; + | ^^^^^^^^^ + +error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast.rs:14:5 + | +LL | x1 as f64; + | ^^^^^^^^^ + +error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:16:5 + | +LL | x2 as f32; + | ^^^^^^^^^ + +error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast.rs:18:5 + | +LL | x3 as f32; + | ^^^^^^^^^ + +error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast.rs:19:5 + | +LL | x3 as f64; + | ^^^^^^^^^ + +error: casting `f32` to `i32` may truncate the value + --> $DIR/cast.rs:21:5 + | +LL | 1f32 as i32; + | ^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` + +error: casting `f32` to `u32` may truncate the value + --> $DIR/cast.rs:22:5 + | +LL | 1f32 as u32; + | ^^^^^^^^^^^ + +error: casting `f32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:22:5 + | +LL | 1f32 as u32; + | ^^^^^^^^^^^ + | + = note: `-D clippy::cast-sign-loss` implied by `-D warnings` + +error: casting `f64` to `f32` may truncate the value + --> $DIR/cast.rs:23:5 + | +LL | 1f64 as f32; + | ^^^^^^^^^^^ + +error: casting `i32` to `i8` may truncate the value + --> $DIR/cast.rs:24:5 + | +LL | 1i32 as i8; + | ^^^^^^^^^^ + +error: casting `i32` to `u8` may truncate the value + --> $DIR/cast.rs:25:5 + | +LL | 1i32 as u8; + | ^^^^^^^^^^ + +error: casting `f64` to `isize` may truncate the value + --> $DIR/cast.rs:26:5 + | +LL | 1f64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `f64` to `usize` may truncate the value + --> $DIR/cast.rs:27:5 + | +LL | 1f64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `f64` to `usize` may lose the sign of the value + --> $DIR/cast.rs:27:5 + | +LL | 1f64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u8` to `i8` may wrap around the value + --> $DIR/cast.rs:29:5 + | +LL | 1u8 as i8; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` + +error: casting `u16` to `i16` may wrap around the value + --> $DIR/cast.rs:30:5 + | +LL | 1u16 as i16; + | ^^^^^^^^^^^ + +error: casting `u32` to `i32` may wrap around the value + --> $DIR/cast.rs:31:5 + | +LL | 1u32 as i32; + | ^^^^^^^^^^^ + +error: casting `u64` to `i64` may wrap around the value + --> $DIR/cast.rs:32:5 + | +LL | 1u64 as i64; + | ^^^^^^^^^^^ + +error: casting `usize` to `isize` may wrap around the value + --> $DIR/cast.rs:33:5 + | +LL | 1usize as isize; + | ^^^^^^^^^^^^^^^ + +error: casting `i32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:36:5 + | +LL | -1i32 as u32; + | ^^^^^^^^^^^^ + +error: casting `isize` to `usize` may lose the sign of the value + --> $DIR/cast.rs:38:5 + | +LL | -1isize as usize; + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_alignment.rs b/src/tools/clippy/tests/ui/cast_alignment.rs new file mode 100644 index 0000000000000..4c08935639f1f --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_alignment.rs @@ -0,0 +1,27 @@ +//! Test casts for alignment issues + +#![feature(rustc_private)] +extern crate libc; + +#[warn(clippy::cast_ptr_alignment)] +#[allow(clippy::no_effect, clippy::unnecessary_operation, clippy::cast_lossless)] +fn main() { + /* These should be warned against */ + + // cast to more-strictly-aligned type + (&1u8 as *const u8) as *const u16; + (&mut 1u8 as *mut u8) as *mut u16; + + /* These should be ok */ + + // not a pointer type + 1u8 as u16; + // cast to less-strictly-aligned type + (&1u16 as *const u16) as *const u8; + (&mut 1u16 as *mut u16) as *mut u8; + // For c_void, we should trust the user. See #2677 + (&1u32 as *const u32 as *const std::os::raw::c_void) as *const u32; + (&1u32 as *const u32 as *const libc::c_void) as *const u32; + // For ZST, we should trust the user. See #4256 + (&1u32 as *const u32 as *const ()) as *const u32; +} diff --git a/src/tools/clippy/tests/ui/cast_alignment.stderr b/src/tools/clippy/tests/ui/cast_alignment.stderr new file mode 100644 index 0000000000000..79219f86155a4 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_alignment.stderr @@ -0,0 +1,16 @@ +error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes) + --> $DIR/cast_alignment.rs:12:5 + | +LL | (&1u8 as *const u8) as *const u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-ptr-alignment` implied by `-D warnings` + +error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes) + --> $DIR/cast_alignment.rs:13:5 + | +LL | (&mut 1u8 as *mut u8) as *mut u16; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_lossless_float.fixed b/src/tools/clippy/tests/ui/cast_lossless_float.fixed new file mode 100644 index 0000000000000..709d58b596c83 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_float.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to floating-point types + let x0 = 1i8; + f32::from(x0); + f64::from(x0); + let x1 = 1u8; + f32::from(x1); + f64::from(x1); + let x2 = 1i16; + f32::from(x2); + f64::from(x2); + let x3 = 1u16; + f32::from(x3); + f64::from(x3); + let x4 = 1i32; + f64::from(x4); + let x5 = 1u32; + f64::from(x5); + + // Test with casts from floating-point types + f64::from(1.0f32); +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: f32) -> f64 { + input as f64 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: f32) -> f64 { + x as f64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_float.rs b/src/tools/clippy/tests/ui/cast_lossless_float.rs new file mode 100644 index 0000000000000..eb0aab8864290 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_float.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to floating-point types + let x0 = 1i8; + x0 as f32; + x0 as f64; + let x1 = 1u8; + x1 as f32; + x1 as f64; + let x2 = 1i16; + x2 as f32; + x2 as f64; + let x3 = 1u16; + x3 as f32; + x3 as f64; + let x4 = 1i32; + x4 as f64; + let x5 = 1u32; + x5 as f64; + + // Test with casts from floating-point types + 1.0f32 as f64; +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: f32) -> f64 { + input as f64 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: f32) -> f64 { + x as f64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_float.stderr b/src/tools/clippy/tests/ui/cast_lossless_float.stderr new file mode 100644 index 0000000000000..0ed09f3083c28 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_float.stderr @@ -0,0 +1,70 @@ +error: casting `i8` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:9:5 + | +LL | x0 as f32; + | ^^^^^^^^^ help: try: `f32::from(x0)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `i8` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:10:5 + | +LL | x0 as f64; + | ^^^^^^^^^ help: try: `f64::from(x0)` + +error: casting `u8` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:12:5 + | +LL | x1 as f32; + | ^^^^^^^^^ help: try: `f32::from(x1)` + +error: casting `u8` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:13:5 + | +LL | x1 as f64; + | ^^^^^^^^^ help: try: `f64::from(x1)` + +error: casting `i16` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:15:5 + | +LL | x2 as f32; + | ^^^^^^^^^ help: try: `f32::from(x2)` + +error: casting `i16` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:16:5 + | +LL | x2 as f64; + | ^^^^^^^^^ help: try: `f64::from(x2)` + +error: casting `u16` to `f32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:18:5 + | +LL | x3 as f32; + | ^^^^^^^^^ help: try: `f32::from(x3)` + +error: casting `u16` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:19:5 + | +LL | x3 as f64; + | ^^^^^^^^^ help: try: `f64::from(x3)` + +error: casting `i32` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:21:5 + | +LL | x4 as f64; + | ^^^^^^^^^ help: try: `f64::from(x4)` + +error: casting `u32` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:23:5 + | +LL | x5 as f64; + | ^^^^^^^^^ help: try: `f64::from(x5)` + +error: casting `f32` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_float.rs:26:5 + | +LL | 1.0f32 as f64; + | ^^^^^^^^^^^^^ help: try: `f64::from(1.0f32)` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_lossless_integer.fixed b/src/tools/clippy/tests/ui/cast_lossless_integer.fixed new file mode 100644 index 0000000000000..03e49adb117db --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_integer.fixed @@ -0,0 +1,47 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to integer types + i16::from(1i8); + i32::from(1i8); + i64::from(1i8); + i16::from(1u8); + i32::from(1u8); + i64::from(1u8); + u16::from(1u8); + u32::from(1u8); + u64::from(1u8); + i32::from(1i16); + i64::from(1i16); + i32::from(1u16); + i64::from(1u16); + u32::from(1u16); + u64::from(1u16); + i64::from(1i32); + i64::from(1u32); + u64::from(1u32); + + // Test with an expression wrapped in parens + u16::from(1u8 + 1u8); +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: u16) -> u32 { + input as u32 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: u32) -> u64 { + x as u64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_integer.rs b/src/tools/clippy/tests/ui/cast_lossless_integer.rs new file mode 100644 index 0000000000000..6a984d245963f --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_integer.rs @@ -0,0 +1,47 @@ +// run-rustfix + +#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to integer types + 1i8 as i16; + 1i8 as i32; + 1i8 as i64; + 1u8 as i16; + 1u8 as i32; + 1u8 as i64; + 1u8 as u16; + 1u8 as u32; + 1u8 as u64; + 1i16 as i32; + 1i16 as i64; + 1u16 as i32; + 1u16 as i64; + 1u16 as u32; + 1u16 as u64; + 1i32 as i64; + 1u32 as i64; + 1u32 as u64; + + // Test with an expression wrapped in parens + (1u8 + 1u8) as u16; +} + +// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: u16) -> u32 { + input as u32 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: u32) -> u64 { + x as u64 + } + } +} diff --git a/src/tools/clippy/tests/ui/cast_lossless_integer.stderr b/src/tools/clippy/tests/ui/cast_lossless_integer.stderr new file mode 100644 index 0000000000000..8e2890f9c28d0 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_lossless_integer.stderr @@ -0,0 +1,118 @@ +error: casting `i8` to `i16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:8:5 + | +LL | 1i8 as i16; + | ^^^^^^^^^^ help: try: `i16::from(1i8)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `i8` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:9:5 + | +LL | 1i8 as i32; + | ^^^^^^^^^^ help: try: `i32::from(1i8)` + +error: casting `i8` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:10:5 + | +LL | 1i8 as i64; + | ^^^^^^^^^^ help: try: `i64::from(1i8)` + +error: casting `u8` to `i16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:11:5 + | +LL | 1u8 as i16; + | ^^^^^^^^^^ help: try: `i16::from(1u8)` + +error: casting `u8` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:12:5 + | +LL | 1u8 as i32; + | ^^^^^^^^^^ help: try: `i32::from(1u8)` + +error: casting `u8` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:13:5 + | +LL | 1u8 as i64; + | ^^^^^^^^^^ help: try: `i64::from(1u8)` + +error: casting `u8` to `u16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:14:5 + | +LL | 1u8 as u16; + | ^^^^^^^^^^ help: try: `u16::from(1u8)` + +error: casting `u8` to `u32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:15:5 + | +LL | 1u8 as u32; + | ^^^^^^^^^^ help: try: `u32::from(1u8)` + +error: casting `u8` to `u64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:16:5 + | +LL | 1u8 as u64; + | ^^^^^^^^^^ help: try: `u64::from(1u8)` + +error: casting `i16` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:17:5 + | +LL | 1i16 as i32; + | ^^^^^^^^^^^ help: try: `i32::from(1i16)` + +error: casting `i16` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:18:5 + | +LL | 1i16 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1i16)` + +error: casting `u16` to `i32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:19:5 + | +LL | 1u16 as i32; + | ^^^^^^^^^^^ help: try: `i32::from(1u16)` + +error: casting `u16` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:20:5 + | +LL | 1u16 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1u16)` + +error: casting `u16` to `u32` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:21:5 + | +LL | 1u16 as u32; + | ^^^^^^^^^^^ help: try: `u32::from(1u16)` + +error: casting `u16` to `u64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:22:5 + | +LL | 1u16 as u64; + | ^^^^^^^^^^^ help: try: `u64::from(1u16)` + +error: casting `i32` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:23:5 + | +LL | 1i32 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1i32)` + +error: casting `u32` to `i64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:24:5 + | +LL | 1u32 as i64; + | ^^^^^^^^^^^ help: try: `i64::from(1u32)` + +error: casting `u32` to `u64` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:25:5 + | +LL | 1u32 as u64; + | ^^^^^^^^^^^ help: try: `u64::from(1u32)` + +error: casting `u8` to `u16` may become silently lossy if you later change the type + --> $DIR/cast_lossless_integer.rs:28:5 + | +LL | (1u8 + 1u8) as u16; + | ^^^^^^^^^^^^^^^^^^ help: try: `u16::from(1u8 + 1u8)` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_ref_to_mut.rs b/src/tools/clippy/tests/ui/cast_ref_to_mut.rs new file mode 100644 index 0000000000000..089e5cfabe4b9 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_ref_to_mut.rs @@ -0,0 +1,31 @@ +#![warn(clippy::cast_ref_to_mut)] +#![allow(clippy::no_effect)] + +extern "C" { + // N.B., mutability can be easily incorrect in FFI calls -- as + // in C, the default is mutable pointers. + fn ffi(c: *mut u8); + fn int_ffi(c: *mut i32); +} + +fn main() { + let s = String::from("Hello"); + let a = &s; + unsafe { + let num = &3i32; + let mut_num = &mut 3i32; + // Should be warned against + (*(a as *const _ as *mut String)).push_str(" world"); + *(a as *const _ as *mut _) = String::from("Replaced"); + *(a as *const _ as *mut String) += " world"; + // Shouldn't be warned against + println!("{}", *(num as *const _ as *const i16)); + println!("{}", *(mut_num as *mut _ as *mut i16)); + ffi(a.as_ptr() as *mut _); + int_ffi(num as *const _ as *mut _); + int_ffi(&3 as *const _ as *mut _); + let mut value = 3; + let value: *const i32 = &mut value; + *(value as *const i16 as *mut i16) = 42; + } +} diff --git a/src/tools/clippy/tests/ui/cast_ref_to_mut.stderr b/src/tools/clippy/tests/ui/cast_ref_to_mut.stderr new file mode 100644 index 0000000000000..aacd99437d9fc --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_ref_to_mut.stderr @@ -0,0 +1,22 @@ +error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` + --> $DIR/cast_ref_to_mut.rs:18:9 + | +LL | (*(a as *const _ as *mut String)).push_str(" world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-ref-to-mut` implied by `-D warnings` + +error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` + --> $DIR/cast_ref_to_mut.rs:19:9 + | +LL | *(a as *const _ as *mut _) = String::from("Replaced"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` + --> $DIR/cast_ref_to_mut.rs:20:9 + | +LL | *(a as *const _ as *mut String) += " world"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_size.rs b/src/tools/clippy/tests/ui/cast_size.rs new file mode 100644 index 0000000000000..595109be46bb8 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size.rs @@ -0,0 +1,35 @@ +// ignore-32bit +#[warn( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::cast_lossless +)] +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + // Casting from *size + 1isize as i8; + let x0 = 1isize; + let x1 = 1usize; + x0 as f64; + x1 as f64; + x0 as f32; + x1 as f32; + 1isize as i32; + 1isize as u32; + 1usize as u32; + 1usize as i32; + // Casting to *size + 1i64 as isize; + 1i64 as usize; + 1u64 as isize; + 1u64 as usize; + 1u32 as isize; + 1u32 as usize; // Should not trigger any lint + 1i32 as isize; // Neither should this + 1i32 as usize; + // Big integer literal to float + 999_999_999 as f32; + 9_999_999_999_999_999usize as f64; +} diff --git a/src/tools/clippy/tests/ui/cast_size.stderr b/src/tools/clippy/tests/ui/cast_size.stderr new file mode 100644 index 0000000000000..95552f2e28539 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size.stderr @@ -0,0 +1,116 @@ +error: casting `isize` to `i8` may truncate the value + --> $DIR/cast_size.rs:12:5 + | +LL | 1isize as i8; + | ^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` + +error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size.rs:15:5 + | +LL | x0 as f64; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-precision-loss` implied by `-D warnings` + +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size.rs:16:5 + | +LL | x1 as f64; + | ^^^^^^^^^ + +error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size.rs:17:5 + | +LL | x0 as f32; + | ^^^^^^^^^ + +error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size.rs:18:5 + | +LL | x1 as f32; + | ^^^^^^^^^ + +error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:19:5 + | +LL | 1isize as i32; + | ^^^^^^^^^^^^^ + +error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:20:5 + | +LL | 1isize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:21:5 + | +LL | 1usize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` + +error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:24:5 + | +LL | 1i64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:25:5 + | +LL | 1i64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers + --> $DIR/cast_size.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:27:5 + | +LL | 1u64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size.rs:28:5 + | +LL | 1u32 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size.rs:33:5 + | +LL | 999_999_999 as f32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size.rs:34:5 + | +LL | 9_999_999_999_999_999usize as f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/cast_size_32bit.rs b/src/tools/clippy/tests/ui/cast_size_32bit.rs new file mode 100644 index 0000000000000..99aac6deca324 --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size_32bit.rs @@ -0,0 +1,35 @@ +// ignore-64bit +#[warn( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::cast_lossless +)] +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + // Casting from *size + 1isize as i8; + let x0 = 1isize; + let x1 = 1usize; + x0 as f64; + x1 as f64; + x0 as f32; + x1 as f32; + 1isize as i32; + 1isize as u32; + 1usize as u32; + 1usize as i32; + // Casting to *size + 1i64 as isize; + 1i64 as usize; + 1u64 as isize; + 1u64 as usize; + 1u32 as isize; + 1u32 as usize; // Should not trigger any lint + 1i32 as isize; // Neither should this + 1i32 as usize; + // Big integer literal to float + 999_999_999 as f32; + 3_999_999_999usize as f64; +} diff --git a/src/tools/clippy/tests/ui/cast_size_32bit.stderr b/src/tools/clippy/tests/ui/cast_size_32bit.stderr new file mode 100644 index 0000000000000..2eec51895f59f --- /dev/null +++ b/src/tools/clippy/tests/ui/cast_size_32bit.stderr @@ -0,0 +1,132 @@ +error: casting `isize` to `i8` may truncate the value + --> $DIR/cast_size_32bit.rs:12:5 + | +LL | 1isize as i8; + | ^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` + +error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size_32bit.rs:15:5 + | +LL | x0 as f64; + | ^^^^^^^^^ + | + = note: `-D clippy::cast-precision-loss` implied by `-D warnings` + +error: casting `isize` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_size_32bit.rs:15:5 + | +LL | x0 as f64; + | ^^^^^^^^^ help: try: `f64::from(x0)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> $DIR/cast_size_32bit.rs:16:5 + | +LL | x1 as f64; + | ^^^^^^^^^ + +error: casting `usize` to `f64` may become silently lossy if you later change the type + --> $DIR/cast_size_32bit.rs:16:5 + | +LL | x1 as f64; + | ^^^^^^^^^ help: try: `f64::from(x1)` + +error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size_32bit.rs:17:5 + | +LL | x0 as f32; + | ^^^^^^^^^ + +error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size_32bit.rs:18:5 + | +LL | x1 as f32; + | ^^^^^^^^^ + +error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:19:5 + | +LL | 1isize as i32; + | ^^^^^^^^^^^^^ + +error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:20:5 + | +LL | 1isize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:21:5 + | +LL | 1usize as u32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + +error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:22:5 + | +LL | 1usize as i32; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` + +error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:24:5 + | +LL | 1i64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:25:5 + | +LL | 1i64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers + --> $DIR/cast_size_32bit.rs:26:5 + | +LL | 1u64 as isize; + | ^^^^^^^^^^^^^ + +error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:27:5 + | +LL | 1u64 as usize; + | ^^^^^^^^^^^^^ + +error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers + --> $DIR/cast_size_32bit.rs:28:5 + | +LL | 1u32 as isize; + | ^^^^^^^^^^^^^ + +error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> $DIR/cast_size_32bit.rs:33:5 + | +LL | 999_999_999 as f32; + | ^^^^^^^^^^^^^^^^^^ + +error: casting integer literal to `f64` is unnecessary + --> $DIR/cast_size_32bit.rs:34:5 + | +LL | 3_999_999_999usize as f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `3999999999_f64` + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed new file mode 100644 index 0000000000000..4e583a25b94c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed @@ -0,0 +1,31 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] + +#![allow(unused, clippy::no_effect)] +#![warn(clippy::deprecated_cfg_attr)] + +// This doesn't get linted, see known problems +#![cfg_attr(rustfmt, rustfmt_skip)] + +#[rustfmt::skip] +trait Foo +{ +fn foo( +); +} + +fn skip_on_statements() { + #[rustfmt::skip] + 5+3; +} + +#[rustfmt::skip] +fn main() { + foo::f(); +} + +mod foo { + #![cfg_attr(rustfmt, rustfmt_skip)] + + pub fn f() {} +} diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs new file mode 100644 index 0000000000000..9c0fcf6fb454c --- /dev/null +++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs @@ -0,0 +1,31 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] + +#![allow(unused, clippy::no_effect)] +#![warn(clippy::deprecated_cfg_attr)] + +// This doesn't get linted, see known problems +#![cfg_attr(rustfmt, rustfmt_skip)] + +#[rustfmt::skip] +trait Foo +{ +fn foo( +); +} + +fn skip_on_statements() { + #[cfg_attr(rustfmt, rustfmt::skip)] + 5+3; +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +fn main() { + foo::f(); +} + +mod foo { + #![cfg_attr(rustfmt, rustfmt_skip)] + + pub fn f() {} +} diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.stderr b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.stderr new file mode 100644 index 0000000000000..c1efd47db90b0 --- /dev/null +++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.stderr @@ -0,0 +1,16 @@ +error: `cfg_attr` is deprecated for rustfmt and got replaced by tool attributes + --> $DIR/cfg_attr_rustfmt.rs:18:5 + | +LL | #[cfg_attr(rustfmt, rustfmt::skip)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `#[rustfmt::skip]` + | + = note: `-D clippy::deprecated-cfg-attr` implied by `-D warnings` + +error: `cfg_attr` is deprecated for rustfmt and got replaced by tool attributes + --> $DIR/cfg_attr_rustfmt.rs:22:1 + | +LL | #[cfg_attr(rustfmt, rustfmt_skip)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `#[rustfmt::skip]` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8.rs b/src/tools/clippy/tests/ui/char_lit_as_u8.rs new file mode 100644 index 0000000000000..0a53a3d6490a3 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8.rs @@ -0,0 +1,5 @@ +#![warn(clippy::char_lit_as_u8)] + +fn main() { + let _ = '❤' as u8; // no suggestion, since a byte literal won't work. +} diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8.stderr new file mode 100644 index 0000000000000..b9836d2f25532 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8.stderr @@ -0,0 +1,11 @@ +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8.rs:4:13 + | +LL | let _ = '❤' as u8; // no suggestion, since a byte literal won't work. + | ^^^^^^^^^ + | + = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` + = note: `char` is four bytes wide, but `u8` is a single byte + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed new file mode 100644 index 0000000000000..3dc3cb4e7573d --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.fixed @@ -0,0 +1,10 @@ +// run-rustfix + +#![warn(clippy::char_lit_as_u8)] + +fn main() { + let _ = b'a'; + let _ = b'\n'; + let _ = b'\0'; + let _ = b'\x01'; +} diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs new file mode 100644 index 0000000000000..d379a0234942a --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.rs @@ -0,0 +1,10 @@ +// run-rustfix + +#![warn(clippy::char_lit_as_u8)] + +fn main() { + let _ = 'a' as u8; + let _ = '\n' as u8; + let _ = '\0' as u8; + let _ = '\x01' as u8; +} diff --git a/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr new file mode 100644 index 0000000000000..bf7cb1607b4e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/char_lit_as_u8_suggestions.stderr @@ -0,0 +1,35 @@ +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:6:13 + | +LL | let _ = 'a' as u8; + | ^^^^^^^^^ help: use a byte literal instead: `b'a'` + | + = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:7:13 + | +LL | let _ = '/n' as u8; + | ^^^^^^^^^^ help: use a byte literal instead: `b'/n'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:8:13 + | +LL | let _ = '/0' as u8; + | ^^^^^^^^^^ help: use a byte literal instead: `b'/0'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> $DIR/char_lit_as_u8_suggestions.rs:9:13 + | +LL | let _ = '/x01' as u8; + | ^^^^^^^^^^^^ help: use a byte literal instead: `b'/x01'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_conversions.fixed b/src/tools/clippy/tests/ui/checked_conversions.fixed new file mode 100644 index 0000000000000..7febd6f376135 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_conversions.fixed @@ -0,0 +1,106 @@ +// run-rustfix + +#![warn(clippy::checked_conversions)] +#![allow(clippy::cast_lossless)] +#![allow(dead_code)] +use std::convert::TryFrom; + +// Positive tests + +// Signed to unsigned + +fn i64_to_u32(value: i64) -> Option { + if u32::try_from(value).is_ok() { + Some(value as u32) + } else { + None + } +} + +fn i64_to_u16(value: i64) -> Option { + if u16::try_from(value).is_ok() { + Some(value as u16) + } else { + None + } +} + +fn isize_to_u8(value: isize) -> Option { + if u8::try_from(value).is_ok() { + Some(value as u8) + } else { + None + } +} + +// Signed to signed + +fn i64_to_i32(value: i64) -> Option { + if i32::try_from(value).is_ok() { + Some(value as i32) + } else { + None + } +} + +fn i64_to_i16(value: i64) -> Option { + if i16::try_from(value).is_ok() { + Some(value as i16) + } else { + None + } +} + +// Unsigned to X + +fn u32_to_i32(value: u32) -> Option { + if i32::try_from(value).is_ok() { + Some(value as i32) + } else { + None + } +} + +fn usize_to_isize(value: usize) -> isize { + if isize::try_from(value).is_ok() && value as i32 == 5 { + 5 + } else { + 1 + } +} + +fn u32_to_u16(value: u32) -> isize { + if u16::try_from(value).is_ok() && value as i32 == 5 { + 5 + } else { + 1 + } +} + +// Negative tests + +fn no_i64_to_i32(value: i64) -> Option { + if value <= (i32::max_value() as i64) && value >= 0 { + Some(value as i32) + } else { + None + } +} + +fn no_isize_to_u8(value: isize) -> Option { + if value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize) { + Some(value as u8) + } else { + None + } +} + +fn i8_to_u8(value: i8) -> Option { + if value >= 0 { + Some(value as u8) + } else { + None + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_conversions.rs b/src/tools/clippy/tests/ui/checked_conversions.rs new file mode 100644 index 0000000000000..a643354e2438f --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_conversions.rs @@ -0,0 +1,106 @@ +// run-rustfix + +#![warn(clippy::checked_conversions)] +#![allow(clippy::cast_lossless)] +#![allow(dead_code)] +use std::convert::TryFrom; + +// Positive tests + +// Signed to unsigned + +fn i64_to_u32(value: i64) -> Option { + if value <= (u32::max_value() as i64) && value >= 0 { + Some(value as u32) + } else { + None + } +} + +fn i64_to_u16(value: i64) -> Option { + if value <= i64::from(u16::max_value()) && value >= 0 { + Some(value as u16) + } else { + None + } +} + +fn isize_to_u8(value: isize) -> Option { + if value <= (u8::max_value() as isize) && value >= 0 { + Some(value as u8) + } else { + None + } +} + +// Signed to signed + +fn i64_to_i32(value: i64) -> Option { + if value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64) { + Some(value as i32) + } else { + None + } +} + +fn i64_to_i16(value: i64) -> Option { + if value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()) { + Some(value as i16) + } else { + None + } +} + +// Unsigned to X + +fn u32_to_i32(value: u32) -> Option { + if value <= i32::max_value() as u32 { + Some(value as i32) + } else { + None + } +} + +fn usize_to_isize(value: usize) -> isize { + if value <= isize::max_value() as usize && value as i32 == 5 { + 5 + } else { + 1 + } +} + +fn u32_to_u16(value: u32) -> isize { + if value <= u16::max_value() as u32 && value as i32 == 5 { + 5 + } else { + 1 + } +} + +// Negative tests + +fn no_i64_to_i32(value: i64) -> Option { + if value <= (i32::max_value() as i64) && value >= 0 { + Some(value as i32) + } else { + None + } +} + +fn no_isize_to_u8(value: isize) -> Option { + if value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize) { + Some(value as u8) + } else { + None + } +} + +fn i8_to_u8(value: i8) -> Option { + if value >= 0 { + Some(value as u8) + } else { + None + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_conversions.stderr b/src/tools/clippy/tests/ui/checked_conversions.stderr new file mode 100644 index 0000000000000..f678f009621f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_conversions.stderr @@ -0,0 +1,52 @@ +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:13:8 + | +LL | if value <= (u32::max_value() as i64) && value >= 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` + | + = note: `-D clippy::checked-conversions` implied by `-D warnings` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:21:8 + | +LL | if value <= i64::from(u16::max_value()) && value >= 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:29:8 + | +LL | if value <= (u8::max_value() as isize) && value >= 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:39:8 + | +LL | if value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:47:8 + | +LL | if value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:57:8 + | +LL | if value <= i32::max_value() as u32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:65:8 + | +LL | if value <= isize::max_value() as usize && value as i32 == 5 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:73:8 + | +LL | if value <= u16::max_value() as u32 && value as i32 == 5 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_conversions.stdout b/src/tools/clippy/tests/ui/checked_conversions.stdout new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.rs b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.rs new file mode 100644 index 0000000000000..c986c992a07a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.rs @@ -0,0 +1,54 @@ +#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![allow(clippy::if_same_then_else)] + +fn test_complex_conditions() { + let x: Result<(), ()> = Ok(()); + let y: Result<(), ()> = Ok(()); + if x.is_ok() && y.is_err() { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + y.unwrap(); // will panic + y.unwrap_err(); // unnecessary + } else { + // not statically determinable whether any of the following will always succeed or always fail: + x.unwrap(); + x.unwrap_err(); + y.unwrap(); + y.unwrap_err(); + } + + if x.is_ok() || y.is_ok() { + // not statically determinable whether any of the following will always succeed or always fail: + x.unwrap(); + y.unwrap(); + } else { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + y.unwrap(); // will panic + y.unwrap_err(); // unnecessary + } + let z: Result<(), ()> = Ok(()); + if x.is_ok() && !(y.is_ok() || z.is_err()) { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + y.unwrap(); // will panic + y.unwrap_err(); // unnecessary + z.unwrap(); // unnecessary + z.unwrap_err(); // will panic + } + if x.is_ok() || !(y.is_ok() && z.is_err()) { + // not statically determinable whether any of the following will always succeed or always fail: + x.unwrap(); + y.unwrap(); + z.unwrap(); + } else { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + y.unwrap(); // unnecessary + y.unwrap_err(); // will panic + z.unwrap(); // will panic + z.unwrap_err(); // unnecessary + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.stderr b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.stderr new file mode 100644 index 0000000000000..dc666bab46039 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals.stderr @@ -0,0 +1,192 @@ +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:8:9 + | +LL | if x.is_ok() && y.is_err() { + | --------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals.rs:1:35 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: This call to `unwrap_err()` will always panic. + --> $DIR/complex_conditionals.rs:9:9 + | +LL | if x.is_ok() && y.is_err() { + | --------- because of this check +LL | x.unwrap(); // unnecessary +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals.rs:1:9 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals.rs:10:9 + | +LL | if x.is_ok() && y.is_err() { + | ---------- because of this check +... +LL | y.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:11:9 + | +LL | if x.is_ok() && y.is_err() { + | ---------- the check is happening here +... +LL | y.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals.rs:25:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:26:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- the check is happening here +... +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals.rs:27:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- because of this check +... +LL | y.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:28:9 + | +LL | if x.is_ok() || y.is_ok() { + | --------- the check is happening here +... +LL | y.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:32:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: This call to `unwrap_err()` will always panic. + --> $DIR/complex_conditionals.rs:33:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- because of this check +LL | x.unwrap(); // unnecessary +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals.rs:34:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- because of this check +... +LL | y.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:35:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | --------- the check is happening here +... +LL | y.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:36:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | ---------- the check is happening here +... +LL | z.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: This call to `unwrap_err()` will always panic. + --> $DIR/complex_conditionals.rs:37:9 + | +LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { + | ---------- because of this check +... +LL | z.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals.rs:45:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:46:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- the check is happening here +... +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:47:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- the check is happening here +... +LL | y.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: This call to `unwrap_err()` will always panic. + --> $DIR/complex_conditionals.rs:48:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | --------- because of this check +... +LL | y.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals.rs:49:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | ---------- because of this check +... +LL | z.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals.rs:50:9 + | +LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { + | ---------- the check is happening here +... +LL | z.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.rs b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.rs new file mode 100644 index 0000000000000..2307996a48ffc --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.rs @@ -0,0 +1,15 @@ +#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![allow(clippy::if_same_then_else)] + +fn test_nested() { + fn nested() { + let x = Some(()); + if x.is_some() { + x.unwrap(); // unnecessary + } else { + x.unwrap(); // will panic + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.stderr b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.stderr new file mode 100644 index 0000000000000..e4d085470c3b4 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/complex_conditionals_nested.stderr @@ -0,0 +1,31 @@ +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/complex_conditionals_nested.rs:8:13 + | +LL | if x.is_some() { + | ----------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals_nested.rs:1:35 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/complex_conditionals_nested.rs:10:13 + | +LL | if x.is_some() { + | ----------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/complex_conditionals_nested.rs:1:9 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs new file mode 100644 index 0000000000000..b0fc26ff76de5 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs @@ -0,0 +1,53 @@ +#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![allow(clippy::if_same_then_else)] + +macro_rules! m { + ($a:expr) => { + if $a.is_some() { + $a.unwrap(); // unnecessary + } + }; +} + +fn main() { + let x = Some(()); + if x.is_some() { + x.unwrap(); // unnecessary + } else { + x.unwrap(); // will panic + } + if x.is_none() { + x.unwrap(); // will panic + } else { + x.unwrap(); // unnecessary + } + m!(x); + let mut x: Result<(), ()> = Ok(()); + if x.is_ok() { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + } else { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + } + if x.is_err() { + x.unwrap(); // will panic + x.unwrap_err(); // unnecessary + } else { + x.unwrap(); // unnecessary + x.unwrap_err(); // will panic + } + if x.is_ok() { + x = Err(()); + x.unwrap(); // not unnecessary because of mutation of x + // it will always panic but the lint is not smart enough to see this (it only + // checks if conditions). + } else { + x = Ok(()); + x.unwrap_err(); // not unnecessary because of mutation of x + // it will always panic but the lint is not smart enough to see this (it + // only checks if conditions). + } + + assert!(x.is_ok(), "{:?}", x.unwrap_err()); // ok, it's a common test pattern +} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr new file mode 100644 index 0000000000000..e40542e2e4f90 --- /dev/null +++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -0,0 +1,131 @@ +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:15:9 + | +LL | if x.is_some() { + | ----------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/simple_conditionals.rs:1:35 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/simple_conditionals.rs:17:9 + | +LL | if x.is_some() { + | ----------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/simple_conditionals.rs:1:9 + | +LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/simple_conditionals.rs:20:9 + | +LL | if x.is_none() { + | ----------- because of this check +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:22:9 + | +LL | if x.is_none() { + | ----------- the check is happening here +... +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:7:13 + | +LL | if $a.is_some() { + | ------------ the check is happening here +LL | $a.unwrap(); // unnecessary + | ^^^^^^^^^^^ +... +LL | m!(x); + | ------ in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:27:9 + | +LL | if x.is_ok() { + | --------- the check is happening here +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: This call to `unwrap_err()` will always panic. + --> $DIR/simple_conditionals.rs:28:9 + | +LL | if x.is_ok() { + | --------- because of this check +LL | x.unwrap(); // unnecessary +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/simple_conditionals.rs:30:9 + | +LL | if x.is_ok() { + | --------- because of this check +... +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:31:9 + | +LL | if x.is_ok() { + | --------- the check is happening here +... +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: This call to `unwrap()` will always panic. + --> $DIR/simple_conditionals.rs:34:9 + | +LL | if x.is_err() { + | ---------- because of this check +LL | x.unwrap(); // will panic + | ^^^^^^^^^^ + +error: You checked before that `unwrap_err()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:35:9 + | +LL | if x.is_err() { + | ---------- the check is happening here +LL | x.unwrap(); // will panic +LL | x.unwrap_err(); // unnecessary + | ^^^^^^^^^^^^^^ + +error: You checked before that `unwrap()` cannot fail. Instead of checking and unwrapping, it's better to use `if let` or `match`. + --> $DIR/simple_conditionals.rs:37:9 + | +LL | if x.is_err() { + | ---------- the check is happening here +... +LL | x.unwrap(); // unnecessary + | ^^^^^^^^^^ + +error: This call to `unwrap_err()` will always panic. + --> $DIR/simple_conditionals.rs:38:9 + | +LL | if x.is_err() { + | ---------- because of this check +... +LL | x.unwrap_err(); // will panic + | ^^^^^^^^^^^^^^ + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/clone_on_copy_impl.rs b/src/tools/clippy/tests/ui/clone_on_copy_impl.rs new file mode 100644 index 0000000000000..8f9f2a0db8c43 --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy_impl.rs @@ -0,0 +1,22 @@ +use std::fmt; +use std::marker::PhantomData; + +pub struct Key { + #[doc(hidden)] + pub __name: &'static str, + #[doc(hidden)] + pub __phantom: PhantomData, +} + +impl Copy for Key {} + +impl Clone for Key { + fn clone(&self) -> Self { + Key { + __name: self.__name, + __phantom: self.__phantom, + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/clone_on_copy_mut.rs b/src/tools/clippy/tests/ui/clone_on_copy_mut.rs new file mode 100644 index 0000000000000..5bfa256623b6b --- /dev/null +++ b/src/tools/clippy/tests/ui/clone_on_copy_mut.rs @@ -0,0 +1,18 @@ +pub fn dec_read_dec(i: &mut i32) -> i32 { + *i -= 1; + let ret = *i; + *i -= 1; + ret +} + +pub fn minus_1(i: &i32) -> i32 { + dec_read_dec(&mut i.clone()) +} + +fn main() { + let mut i = 10; + assert_eq!(minus_1(&i), 9); + assert_eq!(i, 10); + assert_eq!(dec_read_dec(&mut i), 9); + assert_eq!(i, 8); +} diff --git a/src/tools/clippy/tests/ui/cmp_nan.rs b/src/tools/clippy/tests/ui/cmp_nan.rs new file mode 100644 index 0000000000000..64ca52b010a7e --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_nan.rs @@ -0,0 +1,34 @@ +const NAN_F32: f32 = f32::NAN; +const NAN_F64: f64 = f64::NAN; + +#[warn(clippy::cmp_nan)] +#[allow(clippy::float_cmp, clippy::no_effect, clippy::unnecessary_operation)] +fn main() { + let x = 5f32; + x == f32::NAN; + x != f32::NAN; + x < f32::NAN; + x > f32::NAN; + x <= f32::NAN; + x >= f32::NAN; + x == NAN_F32; + x != NAN_F32; + x < NAN_F32; + x > NAN_F32; + x <= NAN_F32; + x >= NAN_F32; + + let y = 0f64; + y == f64::NAN; + y != f64::NAN; + y < f64::NAN; + y > f64::NAN; + y <= f64::NAN; + y >= f64::NAN; + y == NAN_F64; + y != NAN_F64; + y < NAN_F64; + y > NAN_F64; + y <= NAN_F64; + y >= NAN_F64; +} diff --git a/src/tools/clippy/tests/ui/cmp_nan.stderr b/src/tools/clippy/tests/ui/cmp_nan.stderr new file mode 100644 index 0000000000000..867516661a539 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_nan.stderr @@ -0,0 +1,148 @@ +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:8:5 + | +LL | x == f32::NAN; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cmp-nan` implied by `-D warnings` + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:9:5 + | +LL | x != f32::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:10:5 + | +LL | x < f32::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:11:5 + | +LL | x > f32::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:12:5 + | +LL | x <= f32::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:13:5 + | +LL | x >= f32::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:14:5 + | +LL | x == NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:15:5 + | +LL | x != NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:16:5 + | +LL | x < NAN_F32; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:17:5 + | +LL | x > NAN_F32; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:18:5 + | +LL | x <= NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:19:5 + | +LL | x >= NAN_F32; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:22:5 + | +LL | y == f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:23:5 + | +LL | y != f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:24:5 + | +LL | y < f64::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:25:5 + | +LL | y > f64::NAN; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:26:5 + | +LL | y <= f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:27:5 + | +LL | y >= f64::NAN; + | ^^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:28:5 + | +LL | y == NAN_F64; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:29:5 + | +LL | y != NAN_F64; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:30:5 + | +LL | y < NAN_F64; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:31:5 + | +LL | y > NAN_F64; + | ^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:32:5 + | +LL | y <= NAN_F64; + | ^^^^^^^^^^^^ + +error: doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead + --> $DIR/cmp_nan.rs:33:5 + | +LL | y >= NAN_F64; + | ^^^^^^^^^^^^ + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_null.rs b/src/tools/clippy/tests/ui/cmp_null.rs new file mode 100644 index 0000000000000..2d2d04178c35d --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_null.rs @@ -0,0 +1,17 @@ +#![warn(clippy::cmp_null)] +#![allow(unused_mut)] + +use std::ptr; + +fn main() { + let x = 0; + let p: *const usize = &x; + if p == ptr::null() { + println!("This is surprising!"); + } + let mut y = 0; + let mut m: *mut usize = &mut y; + if m == ptr::null_mut() { + println!("This is surprising, too!"); + } +} diff --git a/src/tools/clippy/tests/ui/cmp_null.stderr b/src/tools/clippy/tests/ui/cmp_null.stderr new file mode 100644 index 0000000000000..b563a2ebec2d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_null.stderr @@ -0,0 +1,16 @@ +error: Comparing with null is better expressed by the `.is_null()` method + --> $DIR/cmp_null.rs:9:8 + | +LL | if p == ptr::null() { + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cmp-null` implied by `-D warnings` + +error: Comparing with null is better expressed by the `.is_null()` method + --> $DIR/cmp_null.rs:14:8 + | +LL | if m == ptr::null_mut() { + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed new file mode 100644 index 0000000000000..05fb96339e33e --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.fixed @@ -0,0 +1,72 @@ +// run-rustfix + +#[warn(clippy::cmp_owned)] +#[allow(clippy::unnecessary_operation, clippy::no_effect, unused_must_use, clippy::eq_op)] +fn main() { + fn with_to_string(x: &str) { + x != "foo"; + + "foo" != x; + } + + let x = "oh"; + + with_to_string(x); + + x != "foo"; + + x != "foo"; + + 42.to_string() == "42"; + + Foo == Foo; + + "abc".chars().filter(|c| *c != 'X'); + + "abc".chars().filter(|c| *c != 'X'); +} + +struct Foo; + +impl PartialEq for Foo { + // Allow this here, because it emits the lint + // without a suggestion. This is tested in + // `tests/ui/cmp_owned/without_suggestion.rs` + #[allow(clippy::cmp_owned)] + fn eq(&self, other: &Self) -> bool { + self.to_owned() == *other + } +} + +impl ToOwned for Foo { + type Owned = Bar; + fn to_owned(&self) -> Bar { + Bar + } +} + +#[derive(PartialEq)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Foo) -> bool { + true + } +} + +impl std::borrow::Borrow for Bar { + fn borrow(&self) -> &Foo { + static FOO: Foo = Foo; + &FOO + } +} + +#[derive(PartialEq)] +struct Baz; + +impl ToOwned for Baz { + type Owned = Baz; + fn to_owned(&self) -> Baz { + Baz + } +} diff --git a/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs new file mode 100644 index 0000000000000..0a02825ed82f1 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.rs @@ -0,0 +1,72 @@ +// run-rustfix + +#[warn(clippy::cmp_owned)] +#[allow(clippy::unnecessary_operation, clippy::no_effect, unused_must_use, clippy::eq_op)] +fn main() { + fn with_to_string(x: &str) { + x != "foo".to_string(); + + "foo".to_string() != x; + } + + let x = "oh"; + + with_to_string(x); + + x != "foo".to_owned(); + + x != String::from("foo"); + + 42.to_string() == "42"; + + Foo.to_owned() == Foo; + + "abc".chars().filter(|c| c.to_owned() != 'X'); + + "abc".chars().filter(|c| *c != 'X'); +} + +struct Foo; + +impl PartialEq for Foo { + // Allow this here, because it emits the lint + // without a suggestion. This is tested in + // `tests/ui/cmp_owned/without_suggestion.rs` + #[allow(clippy::cmp_owned)] + fn eq(&self, other: &Self) -> bool { + self.to_owned() == *other + } +} + +impl ToOwned for Foo { + type Owned = Bar; + fn to_owned(&self) -> Bar { + Bar + } +} + +#[derive(PartialEq)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Foo) -> bool { + true + } +} + +impl std::borrow::Borrow for Bar { + fn borrow(&self) -> &Foo { + static FOO: Foo = Foo; + &FOO + } +} + +#[derive(PartialEq)] +struct Baz; + +impl ToOwned for Baz { + type Owned = Baz; + fn to_owned(&self) -> Baz { + Baz + } +} diff --git a/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr new file mode 100644 index 0000000000000..2f333e6ea8ecb --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/with_suggestion.stderr @@ -0,0 +1,40 @@ +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:7:14 + | +LL | x != "foo".to_string(); + | ^^^^^^^^^^^^^^^^^ help: try: `"foo"` + | + = note: `-D clippy::cmp-owned` implied by `-D warnings` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:9:9 + | +LL | "foo".to_string() != x; + | ^^^^^^^^^^^^^^^^^ help: try: `"foo"` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:16:10 + | +LL | x != "foo".to_owned(); + | ^^^^^^^^^^^^^^^^ help: try: `"foo"` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:18:10 + | +LL | x != String::from("foo"); + | ^^^^^^^^^^^^^^^^^^^ help: try: `"foo"` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:22:5 + | +LL | Foo.to_owned() == Foo; + | ^^^^^^^^^^^^^^ help: try: `Foo` + +error: this creates an owned instance just for comparison + --> $DIR/with_suggestion.rs:24:30 + | +LL | "abc".chars().filter(|c| c.to_owned() != 'X'); + | ^^^^^^^^^^^^ help: try: `*c` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.rs b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.rs new file mode 100644 index 0000000000000..9ab8795474c67 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.rs @@ -0,0 +1,52 @@ +#[allow(clippy::unnecessary_operation)] + +fn main() { + let x = &Baz; + let y = &Baz; + y.to_owned() == *x; + + let x = &&Baz; + let y = &Baz; + y.to_owned() == **x; +} + +struct Foo; + +impl PartialEq for Foo { + fn eq(&self, other: &Self) -> bool { + self.to_owned() == *other + } +} + +impl ToOwned for Foo { + type Owned = Bar; + fn to_owned(&self) -> Bar { + Bar + } +} + +#[derive(PartialEq)] +struct Baz; + +impl ToOwned for Baz { + type Owned = Baz; + fn to_owned(&self) -> Baz { + Baz + } +} + +#[derive(PartialEq)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Foo) -> bool { + true + } +} + +impl std::borrow::Borrow for Bar { + fn borrow(&self) -> &Foo { + static FOO: Foo = Foo; + &FOO + } +} diff --git a/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.stderr b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.stderr new file mode 100644 index 0000000000000..6e8a5ad2a17b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/cmp_owned/without_suggestion.stderr @@ -0,0 +1,22 @@ +error: this creates an owned instance just for comparison + --> $DIR/without_suggestion.rs:6:5 + | +LL | y.to_owned() == *x; + | ^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating + | + = note: `-D clippy::cmp-owned` implied by `-D warnings` + +error: this creates an owned instance just for comparison + --> $DIR/without_suggestion.rs:10:5 + | +LL | y.to_owned() == **x; + | ^^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating + +error: this creates an owned instance just for comparison + --> $DIR/without_suggestion.rs:17:9 + | +LL | self.to_owned() == *other + | ^^^^^^^^^^^^^^^^^^^^^^^^^ try implementing the comparison without allocating + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/cognitive_complexity.rs b/src/tools/clippy/tests/ui/cognitive_complexity.rs new file mode 100644 index 0000000000000..1d3fe405521cd --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity.rs @@ -0,0 +1,395 @@ +#![allow(clippy::all)] +#![warn(clippy::cognitive_complexity)] +#![allow(unused)] + +#[rustfmt::skip] +fn main() { + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } + if true { + println!("a"); + } +} + +#[clippy::cognitive_complexity = "1"] +fn kaboom() { + let n = 0; + 'a: for i in 0..20 { + 'b: for j in i..20 { + for k in j..20 { + if k == 5 { + break 'b; + } + if j == 3 && k == 6 { + continue 'a; + } + if k == j { + continue; + } + println!("bake"); + } + } + println!("cake"); + } +} + +fn bloo() { + match 42 { + 0 => println!("hi"), + 1 => println!("hai"), + 2 => println!("hey"), + 3 => println!("hallo"), + 4 => println!("hello"), + 5 => println!("salut"), + 6 => println!("good morning"), + 7 => println!("good evening"), + 8 => println!("good afternoon"), + 9 => println!("good night"), + 10 => println!("bonjour"), + 11 => println!("hej"), + 12 => println!("hej hej"), + 13 => println!("greetings earthling"), + 14 => println!("take us to you leader"), + 15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 33 => println!("take us to you leader"), + 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 | 51 | 53 => println!("there is no undefined behavior"), + 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 => println!("I know borrow-fu"), + _ => println!("bye"), + } +} + +// Short circuiting operations don't increase the complexity of a function. +// Note that the minimum complexity of a function is 1. +#[clippy::cognitive_complexity = "1"] +fn lots_of_short_circuits() -> bool { + true && false && true && false && true && false && true +} + +#[clippy::cognitive_complexity = "1"] +fn lots_of_short_circuits2() -> bool { + true || false || true || false || true || false || true +} + +#[clippy::cognitive_complexity = "1"] +fn baa() { + let x = || match 99 { + 0 => 0, + 1 => 1, + 2 => 2, + 4 => 4, + 6 => 6, + 9 => 9, + _ => 42, + }; + if x() == 42 { + println!("x"); + } else { + println!("not x"); + } +} + +#[clippy::cognitive_complexity = "1"] +fn bar() { + match 99 { + 0 => println!("hi"), + _ => println!("bye"), + } +} + +#[test] +#[clippy::cognitive_complexity = "1"] +/// Tests are usually complex but simple at the same time. `clippy::cognitive_complexity` used to +/// give lots of false-positives in tests. +fn dont_warn_on_tests() { + match 99 { + 0 => println!("hi"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barr() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barr2() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrr() { + match 99 { + 0 => println!("hi"), + 1 => panic!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrr2() { + match 99 { + 0 => println!("hi"), + 1 => panic!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } + match 99 { + 0 => println!("hi"), + 1 => panic!("bla"), + 2 | 3 => println!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrrr() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => panic!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn barrrr2() { + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => panic!("blub"), + _ => println!("bye"), + } + match 99 { + 0 => println!("hi"), + 1 => println!("bla"), + 2 | 3 => panic!("blub"), + _ => println!("bye"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn cake() { + if 4 == 5 { + println!("yea"); + } else { + panic!("meh"); + } + println!("whee"); +} + +#[clippy::cognitive_complexity = "1"] +pub fn read_file(input_path: &str) -> String { + use std::fs::File; + use std::io::{Read, Write}; + use std::path::Path; + let mut file = match File::open(&Path::new(input_path)) { + Ok(f) => f, + Err(err) => { + panic!("Can't open {}: {}", input_path, err); + }, + }; + + let mut bytes = Vec::new(); + + match file.read_to_end(&mut bytes) { + Ok(..) => {}, + Err(_) => { + panic!("Can't read {}", input_path); + }, + }; + + match String::from_utf8(bytes) { + Ok(contents) => contents, + Err(_) => { + panic!("{} is not UTF-8 encoded", input_path); + }, + } +} + +enum Void {} + +#[clippy::cognitive_complexity = "1"] +fn void(void: Void) { + if true { + match void {} + } +} + +#[clippy::cognitive_complexity = "1"] +fn mcarton_sees_all() { + panic!("meh"); + panic!("möh"); +} + +#[clippy::cognitive_complexity = "1"] +fn try_() -> Result { + match 5 { + 5 => Ok(5), + _ => return Err("bla"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn try_again() -> Result { + let _ = Ok(42)?; + let _ = Ok(43)?; + let _ = Ok(44)?; + let _ = Ok(45)?; + let _ = Ok(46)?; + let _ = Ok(47)?; + let _ = Ok(48)?; + let _ = Ok(49)?; + match 5 { + 5 => Ok(5), + _ => return Err("bla"), + } +} + +#[clippy::cognitive_complexity = "1"] +fn early() -> Result { + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); + return Ok(5); +} + +#[rustfmt::skip] +#[clippy::cognitive_complexity = "1"] +fn early_ret() -> i32 { + let a = if true { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + let a = if a < 99 { 42 } else { return 0; }; + match 5 { + 5 => 5, + _ => return 6, + } +} + +#[clippy::cognitive_complexity = "1"] +fn closures() { + let x = |a: i32, b: i32| -> i32 { + if true { + println!("moo"); + } + + a + b + }; +} + +struct Moo; + +#[clippy::cognitive_complexity = "1"] +impl Moo { + fn moo(&self) { + if true { + println!("moo"); + } + } +} diff --git a/src/tools/clippy/tests/ui/cognitive_complexity.stderr b/src/tools/clippy/tests/ui/cognitive_complexity.stderr new file mode 100644 index 0000000000000..a0ddc673abcc1 --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity.stderr @@ -0,0 +1,139 @@ +error: the function has a cognitive complexity of (28/25) + --> $DIR/cognitive_complexity.rs:6:4 + | +LL | fn main() { + | ^^^^ + | + = note: `-D clippy::cognitive-complexity` implied by `-D warnings` + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (7/1) + --> $DIR/cognitive_complexity.rs:91:4 + | +LL | fn kaboom() { + | ^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:149:4 + | +LL | fn baa() { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:150:13 + | +LL | let x = || match 99 { + | ^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:167:4 + | +LL | fn bar() { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:186:4 + | +LL | fn barr() { + | ^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (3/1) + --> $DIR/cognitive_complexity.rs:196:4 + | +LL | fn barr2() { + | ^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:212:4 + | +LL | fn barrr() { + | ^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (3/1) + --> $DIR/cognitive_complexity.rs:222:4 + | +LL | fn barrr2() { + | ^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:238:4 + | +LL | fn barrrr() { + | ^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (3/1) + --> $DIR/cognitive_complexity.rs:248:4 + | +LL | fn barrrr2() { + | ^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:264:4 + | +LL | fn cake() { + | ^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (4/1) + --> $DIR/cognitive_complexity.rs:274:8 + | +LL | pub fn read_file(input_path: &str) -> String { + | ^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:305:4 + | +LL | fn void(void: Void) { + | ^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (8/1) + --> $DIR/cognitive_complexity.rs:356:4 + | +LL | fn early_ret() -> i32 { + | ^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:377:13 + | +LL | let x = |a: i32, b: i32| -> i32 { + | ^^^^^^^^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> $DIR/cognitive_complexity.rs:390:8 + | +LL | fn moo(&self) { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.rs b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.rs new file mode 100644 index 0000000000000..403eff566ed6d --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.rs @@ -0,0 +1,15 @@ +#![warn(clippy::cognitive_complexity)] +#![warn(unused)] + +fn main() { + kaboom(); +} + +#[clippy::cognitive_complexity = "0"] +fn kaboom() { + if 42 == 43 { + panic!(); + } else if "cake" == "lie" { + println!("what?"); + } +} diff --git a/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.stderr b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.stderr new file mode 100644 index 0000000000000..f5ff53dda603b --- /dev/null +++ b/src/tools/clippy/tests/ui/cognitive_complexity_attr_used.stderr @@ -0,0 +1,11 @@ +error: the function has a cognitive complexity of (3/0) + --> $DIR/cognitive_complexity_attr_used.rs:9:4 + | +LL | fn kaboom() { + | ^^^^^^ + | + = note: `-D clippy::cognitive-complexity` implied by `-D warnings` + = help: you could split it up into multiple smaller functions + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.fixed b/src/tools/clippy/tests/ui/collapsible_else_if.fixed new file mode 100644 index 0000000000000..ce2a1c28c8a80 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_else_if.fixed @@ -0,0 +1,66 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + // Collapse `else { if .. }` to `else if ..` + if x == "hello" { + print!("Hello "); + } else if y == "world" { + println!("world!") + } + + if x == "hello" { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world!") + } + + if x == "hello" { + print!("Hello "); + } else if y == "world" { + println!("world") + } + else { + println!("!") + } + + if x == "hello" { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else if x == "hello" { + println!("world") + } + else { + println!("!") + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.rs b/src/tools/clippy/tests/ui/collapsible_else_if.rs new file mode 100644 index 0000000000000..99c40b8d38eb9 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_else_if.rs @@ -0,0 +1,80 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + // Collapse `else { if .. }` to `else if ..` + if x == "hello" { + print!("Hello "); + } else { + if y == "world" { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + if y == "world" { + println!("world") + } + else { + println!("!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else { + if x == "hello" { + println!("world") + } + else { + println!("!") + } + } + + if let Some(42) = Some(42) { + print!("Hello "); + } else { + if let Some(42) = Some(42) { + println!("world") + } + else { + println!("!") + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_else_if.stderr b/src/tools/clippy/tests/ui/collapsible_else_if.stderr new file mode 100644 index 0000000000000..28048999e8ec9 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_else_if.stderr @@ -0,0 +1,154 @@ +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:12:12 + | +LL | } else { + | ____________^ +LL | | if y == "world" { +LL | | println!("world!") +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::collapsible-if` implied by `-D warnings` +help: try + | +LL | } else if y == "world" { +LL | println!("world!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:20:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world!") +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:28:12 + | +LL | } else { + | ____________^ +LL | | if y == "world" { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | } else if y == "world" { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:39:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:50:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:61:12 + | +LL | } else { + | ____________^ +LL | | if x == "hello" { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | } else if x == "hello" { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: this `else { if .. }` block can be collapsed + --> $DIR/collapsible_else_if.rs:72:12 + | +LL | } else { + | ____________^ +LL | | if let Some(42) = Some(42) { +LL | | println!("world") +LL | | } +... | +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | } else if let Some(42) = Some(42) { +LL | println!("world") +LL | } +LL | else { +LL | println!("!") +LL | } + | + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/collapsible_if.fixed b/src/tools/clippy/tests/ui/collapsible_if.fixed new file mode 100644 index 0000000000000..561283fc8e73d --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_if.fixed @@ -0,0 +1,138 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + if x == "hello" && y == "world" { + println!("Hello world!"); + } + + if (x == "hello" || x == "world") && (y == "world" || y == "hello") { + println!("Hello world!"); + } + + if x == "hello" && x == "world" && (y == "world" || y == "hello") { + println!("Hello world!"); + } + + if (x == "hello" || x == "world") && y == "world" && y == "hello" { + println!("Hello world!"); + } + + if x == "hello" && x == "world" && y == "world" && y == "hello" { + println!("Hello world!"); + } + + if 42 == 1337 && 'a' != 'A' { + println!("world!") + } + + // Works because any if with an else statement cannot be collapsed. + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } else { + println!("Not Hello world"); + } + + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } else { + println!("Hello something else"); + } + } + + if x == "hello" { + print!("Hello "); + if y == "world" { + println!("world!") + } + } + + if true { + } else { + assert!(true); // assert! is just an `if` + } + + + // The following tests check for the fix of https://github.com/rust-lang/rust-clippy/issues/798 + if x == "hello" {// Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { + // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" && y == "world" { // Collapsible + println!("Hello world!"); + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if y == "world" { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if let Some(42) = Some(42) { + println!("world!") + } + } + + if x == "hello" { + /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + // Test behavior wrt. `let_chains`. + // None of the cases below should be collapsed. + fn truth() -> bool { true } + + // Prefix: + if let 0 = 1 { + if truth() {} + } + + // Suffix: + if truth() { + if let 0 = 1 {} + } + + // Midfix: + if truth() { + if let 0 = 1 { + if truth() {} + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_if.rs b/src/tools/clippy/tests/ui/collapsible_if.rs new file mode 100644 index 0000000000000..dc9d9b451c0f9 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_if.rs @@ -0,0 +1,152 @@ +// run-rustfix +#![allow(clippy::assertions_on_constants)] + +#[rustfmt::skip] +#[warn(clippy::collapsible_if)] +fn main() { + let x = "hello"; + let y = "world"; + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" || x == "world" { + if y == "world" || y == "hello" { + println!("Hello world!"); + } + } + + if x == "hello" && x == "world" { + if y == "world" || y == "hello" { + println!("Hello world!"); + } + } + + if x == "hello" || x == "world" { + if y == "world" && y == "hello" { + println!("Hello world!"); + } + } + + if x == "hello" && x == "world" { + if y == "world" && y == "hello" { + println!("Hello world!"); + } + } + + if 42 == 1337 { + if 'a' != 'A' { + println!("world!") + } + } + + // Works because any if with an else statement cannot be collapsed. + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } + } else { + println!("Not Hello world"); + } + + if x == "hello" { + if y == "world" { + println!("Hello world!"); + } else { + println!("Hello something else"); + } + } + + if x == "hello" { + print!("Hello "); + if y == "world" { + println!("world!") + } + } + + if true { + } else { + assert!(true); // assert! is just an `if` + } + + + // The following tests check for the fix of https://github.com/rust-lang/rust-clippy/issues/798 + if x == "hello" {// Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { + // Not collapsible + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { + if y == "world" { // Collapsible + println!("Hello world!"); + } + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if y == "world" { + println!("world!") + } + } + + if x == "hello" { + print!("Hello "); + } else { + // Not collapsible + if let Some(42) = Some(42) { + println!("world!") + } + } + + if x == "hello" { + /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + if x == "hello" { /* Not collapsible */ + if y == "world" { + println!("Hello world!"); + } + } + + // Test behavior wrt. `let_chains`. + // None of the cases below should be collapsed. + fn truth() -> bool { true } + + // Prefix: + if let 0 = 1 { + if truth() {} + } + + // Suffix: + if truth() { + if let 0 = 1 {} + } + + // Midfix: + if truth() { + if let 0 = 1 { + if truth() {} + } + } +} diff --git a/src/tools/clippy/tests/ui/collapsible_if.stderr b/src/tools/clippy/tests/ui/collapsible_if.stderr new file mode 100644 index 0000000000000..6440ff41be81e --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_if.stderr @@ -0,0 +1,122 @@ +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:9:5 + | +LL | / if x == "hello" { +LL | | if y == "world" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::collapsible-if` implied by `-D warnings` +help: try + | +LL | if x == "hello" && y == "world" { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:15:5 + | +LL | / if x == "hello" || x == "world" { +LL | | if y == "world" || y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | if (x == "hello" || x == "world") && (y == "world" || y == "hello") { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:21:5 + | +LL | / if x == "hello" && x == "world" { +LL | | if y == "world" || y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | if x == "hello" && x == "world" && (y == "world" || y == "hello") { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:27:5 + | +LL | / if x == "hello" || x == "world" { +LL | | if y == "world" && y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | if (x == "hello" || x == "world") && y == "world" && y == "hello" { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:33:5 + | +LL | / if x == "hello" && x == "world" { +LL | | if y == "world" && y == "hello" { +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | if x == "hello" && x == "world" && y == "world" && y == "hello" { +LL | println!("Hello world!"); +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:39:5 + | +LL | / if 42 == 1337 { +LL | | if 'a' != 'A' { +LL | | println!("world!") +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | if 42 == 1337 && 'a' != 'A' { +LL | println!("world!") +LL | } + | + +error: this `if` statement can be collapsed + --> $DIR/collapsible_if.rs:95:5 + | +LL | / if x == "hello" { +LL | | if y == "world" { // Collapsible +LL | | println!("Hello world!"); +LL | | } +LL | | } + | |_____^ + | +help: try + | +LL | if x == "hello" && y == "world" { // Collapsible +LL | println!("Hello world!"); +LL | } + | + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/collapsible_span_lint_calls.fixed b/src/tools/clippy/tests/ui/collapsible_span_lint_calls.fixed new file mode 100644 index 0000000000000..e588c23345e2f --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_span_lint_calls.fixed @@ -0,0 +1,91 @@ +// run-rustfix +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_errors; +extern crate rustc_lint; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_ast::ast::Expr; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +#[allow(unused_variables)] +pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F) +where + F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>), +{ +} + +#[allow(unused_variables)] +fn span_lint_and_help<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + option_span: Option, + help: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_note<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + note_span: Option, + note: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_sugg<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + sp: Span, + msg: &str, + help: &str, + sugg: String, + applicability: Applicability, +) { +} + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl EarlyLintPass for Pass { + fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) { + let lint_msg = "lint message"; + let help_msg = "help message"; + let note_msg = "note message"; + let sugg = "new_call()"; + let predicate = true; + + span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable); + span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg); + span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg); + span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg); + span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg); + + // This expr shouldn't trigger this lint. + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.note(note_msg); + if predicate { + db.note(note_msg); + } + }) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/collapsible_span_lint_calls.rs b/src/tools/clippy/tests/ui/collapsible_span_lint_calls.rs new file mode 100644 index 0000000000000..d5dd3bb562b42 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_span_lint_calls.rs @@ -0,0 +1,101 @@ +// run-rustfix +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_errors; +extern crate rustc_lint; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_ast::ast::Expr; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +#[allow(unused_variables)] +pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F) +where + F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>), +{ +} + +#[allow(unused_variables)] +fn span_lint_and_help<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + option_span: Option, + help: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_note<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + span: Span, + msg: &str, + note_span: Option, + note: &str, +) { +} + +#[allow(unused_variables)] +fn span_lint_and_sugg<'a, T: LintContext>( + cx: &'a T, + lint: &'static Lint, + sp: Span, + msg: &str, + help: &str, + sugg: String, + applicability: Applicability, +) { +} + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl EarlyLintPass for Pass { + fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) { + let lint_msg = "lint message"; + let help_msg = "help message"; + let note_msg = "note message"; + let sugg = "new_call()"; + let predicate = true; + + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.span_help(expr.span, help_msg); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.help(help_msg); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.span_note(expr.span, note_msg); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.note(note_msg); + }); + + // This expr shouldn't trigger this lint. + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.note(note_msg); + if predicate { + db.note(note_msg); + } + }) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/collapsible_span_lint_calls.stderr b/src/tools/clippy/tests/ui/collapsible_span_lint_calls.stderr new file mode 100644 index 0000000000000..874d4a9f255c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/collapsible_span_lint_calls.stderr @@ -0,0 +1,49 @@ +error: this call is collapsible + --> $DIR/collapsible_span_lint_calls.rs:75:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)` + | +note: the lint level is defined here + --> $DIR/collapsible_span_lint_calls.rs:2:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]` + +error: this call is collapsible + --> $DIR/collapsible_span_lint_calls.rs:78:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.span_help(expr.span, help_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)` + +error: this call is collapsible + --> $DIR/collapsible_span_lint_calls.rs:81:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.help(help_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)` + +error: this call is collspible + --> $DIR/collapsible_span_lint_calls.rs:84:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.span_note(expr.span, note_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)` + +error: this call is collspible + --> $DIR/collapsible_span_lint_calls.rs:87:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | db.note(note_msg); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/comparison_chain.rs b/src/tools/clippy/tests/ui/comparison_chain.rs new file mode 100644 index 0000000000000..9c2128469de9d --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_chain.rs @@ -0,0 +1,140 @@ +#![allow(dead_code)] +#![warn(clippy::comparison_chain)] + +fn a() {} +fn b() {} +fn c() {} + +fn f(x: u8, y: u8, z: u8) { + // Ignored: Only one branch + if x > y { + a() + } + + if x > y { + a() + } else if x < y { + b() + } + + // Ignored: Only one explicit conditional + if x > y { + a() + } else { + b() + } + + if x > y { + a() + } else if x < y { + b() + } else { + c() + } + + if x > y { + a() + } else if y > x { + b() + } else { + c() + } + + if x > 1 { + a() + } else if x < 1 { + b() + } else if x == 1 { + c() + } + + // Ignored: Binop args are not equivalent + if x > 1 { + a() + } else if y > 1 { + b() + } else { + c() + } + + // Ignored: Binop args are not equivalent + if x > y { + a() + } else if x > z { + b() + } else if y > z { + c() + } + + // Ignored: Not binary comparisons + if true { + a() + } else if false { + b() + } else { + c() + } +} + +#[allow(clippy::float_cmp)] +fn g(x: f64, y: f64, z: f64) { + // Ignored: f64 doesn't implement Ord + if x > y { + a() + } else if x < y { + b() + } + + // Ignored: f64 doesn't implement Ord + if x > y { + a() + } else if x < y { + b() + } else { + c() + } + + // Ignored: f64 doesn't implement Ord + if x > y { + a() + } else if y > x { + b() + } else { + c() + } + + // Ignored: f64 doesn't implement Ord + if x > 1.0 { + a() + } else if x < 1.0 { + b() + } else if x == 1.0 { + c() + } +} + +fn h(x: T, y: T, z: T) { + if x > y { + a() + } else if x < y { + b() + } + + if x > y { + a() + } else if x < y { + b() + } else { + c() + } + + if x > y { + a() + } else if y > x { + b() + } else { + c() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/comparison_chain.stderr b/src/tools/clippy/tests/ui/comparison_chain.stderr new file mode 100644 index 0000000000000..69db88b03b5b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/comparison_chain.stderr @@ -0,0 +1,97 @@ +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:14:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } + | |_____^ + | + = note: `-D clippy::comparison-chain` implied by `-D warnings` + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:27:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:35:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if y > x { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:43:5 + | +LL | / if x > 1 { +LL | | a() +LL | | } else if x < 1 { +LL | | b() +LL | | } else if x == 1 { +LL | | c() +LL | | } + | |_____^ + | + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:117:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } + | |_____^ + | + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:123:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if x < y { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: `if` chain can be rewritten with `match` + --> $DIR/comparison_chain.rs:131:5 + | +LL | / if x > y { +LL | | a() +LL | | } else if y > x { +LL | | b() +LL | | } else { +LL | | c() +LL | | } + | |_____^ + | + = help: Consider rewriting the `if` chain to use `cmp` and `match`. + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/complex_types.rs b/src/tools/clippy/tests/ui/complex_types.rs new file mode 100644 index 0000000000000..be61fb6b9be61 --- /dev/null +++ b/src/tools/clippy/tests/ui/complex_types.rs @@ -0,0 +1,60 @@ +#![warn(clippy::all)] +#![allow(unused, clippy::needless_pass_by_value, clippy::vec_box)] +#![feature(associated_type_defaults)] + +type Alias = Vec>>; // no warning here + +const CST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); +static ST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + +struct S { + f: Vec>>, +} + +struct TS(Vec>>); + +enum E { + Tuple(Vec>>), + Struct { f: Vec>> }, +} + +impl S { + const A: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + fn impl_method(&self, p: Vec>>) {} +} + +trait T { + const A: Vec>>; + type B = Vec>>; + fn method(&self, p: Vec>>); + fn def_method(&self, p: Vec>>) {} +} + +fn test1() -> Vec>> { + vec![] +} + +fn test2(_x: Vec>>) {} + +fn test3() { + let _y: Vec>> = vec![]; +} + +#[repr(C)] +struct D { + // should not warn, since we don't have control over the signature (#3222) + test4: extern "C" fn( + itself: &D, + a: usize, + b: usize, + c: usize, + d: usize, + e: usize, + f: usize, + g: usize, + h: usize, + i: usize, + ), +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/complex_types.stderr b/src/tools/clippy/tests/ui/complex_types.stderr new file mode 100644 index 0000000000000..8f5dbd27956c5 --- /dev/null +++ b/src/tools/clippy/tests/ui/complex_types.stderr @@ -0,0 +1,94 @@ +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:7:12 + | +LL | const CST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::type-complexity` implied by `-D warnings` + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:8:12 + | +LL | static ST: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:11:8 + | +LL | f: Vec>>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:14:11 + | +LL | struct TS(Vec>>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:17:11 + | +LL | Tuple(Vec>>), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:18:17 + | +LL | Struct { f: Vec>> }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:22:14 + | +LL | const A: (u32, (u32, (u32, (u32, u32)))) = (0, (0, (0, (0, 0)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:23:30 + | +LL | fn impl_method(&self, p: Vec>>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:27:14 + | +LL | const A: Vec>>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:28:14 + | +LL | type B = Vec>>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:29:25 + | +LL | fn method(&self, p: Vec>>); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:30:29 + | +LL | fn def_method(&self, p: Vec>>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:33:15 + | +LL | fn test1() -> Vec>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:37:14 + | +LL | fn test2(_x: Vec>>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: very complex type used. Consider factoring parts into `type` definitions + --> $DIR/complex_types.rs:40:13 + | +LL | let _y: Vec>> = vec![]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/copy_iterator.rs b/src/tools/clippy/tests/ui/copy_iterator.rs new file mode 100644 index 0000000000000..e3d5928be231b --- /dev/null +++ b/src/tools/clippy/tests/ui/copy_iterator.rs @@ -0,0 +1,23 @@ +#![warn(clippy::copy_iterator)] + +#[derive(Copy, Clone)] +struct Countdown(u8); + +impl Iterator for Countdown { + type Item = u8; + + fn next(&mut self) -> Option { + self.0.checked_sub(1).map(|c| { + self.0 = c; + c + }) + } +} + +fn main() { + let my_iterator = Countdown(5); + let a: Vec<_> = my_iterator.take(1).collect(); + assert_eq!(a.len(), 1); + let b: Vec<_> = my_iterator.collect(); + assert_eq!(b.len(), 5); +} diff --git a/src/tools/clippy/tests/ui/copy_iterator.stderr b/src/tools/clippy/tests/ui/copy_iterator.stderr new file mode 100644 index 0000000000000..f8ce6af7961a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/copy_iterator.stderr @@ -0,0 +1,17 @@ +error: you are implementing `Iterator` on a `Copy` type + --> $DIR/copy_iterator.rs:6:1 + | +LL | / impl Iterator for Countdown { +LL | | type Item = u8; +LL | | +LL | | fn next(&mut self) -> Option { +... | +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::copy-iterator` implied by `-D warnings` + = note: consider implementing `IntoIterator` instead + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/associated-constant-ice.rs b/src/tools/clippy/tests/ui/crashes/associated-constant-ice.rs new file mode 100644 index 0000000000000..4bb833795bb16 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/associated-constant-ice.rs @@ -0,0 +1,15 @@ +// run-pass + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1698 + +pub trait Trait { + const CONSTANT: u8; +} + +impl Trait for u8 { + const CONSTANT: u8 = 2; +} + +fn main() { + println!("{}", u8::CONSTANT * 10); +} diff --git a/src/tools/clippy/tests/ui/crashes/auxiliary/ice-4727-aux.rs b/src/tools/clippy/tests/ui/crashes/auxiliary/ice-4727-aux.rs new file mode 100644 index 0000000000000..58a20caf6c676 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/auxiliary/ice-4727-aux.rs @@ -0,0 +1,9 @@ +pub trait Trait { + fn fun(par: &str) -> &str; +} + +impl Trait for str { + fn fun(par: &str) -> &str { + &par[0..1] + } +} diff --git a/src/tools/clippy/tests/ui/crashes/auxiliary/proc_macro_crash.rs b/src/tools/clippy/tests/ui/crashes/auxiliary/proc_macro_crash.rs new file mode 100644 index 0000000000000..086548e58ed67 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/auxiliary/proc_macro_crash.rs @@ -0,0 +1,37 @@ +// no-prefer-dynamic +// ^ compiletest by default builds all aux files as dylibs, but we don't want that for proc-macro +// crates. If we don't set this, compiletest will override the `crate_type` attribute below and +// compile this as dylib. Removing this then causes the test to fail because a `dylib` crate can't +// contain a proc-macro. + +#![feature(repr128)] +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; +use std::iter::FromIterator; + +#[proc_macro] +pub fn macro_test(input_stream: TokenStream) -> TokenStream { + let first_token = input_stream.into_iter().next().unwrap(); + let span = first_token.span(); + + TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("fn", Span::call_site())), + TokenTree::Ident(Ident::new("code", Span::call_site())), + TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())), + TokenTree::Group(Group::new(Delimiter::Brace, { + let mut clause = Group::new(Delimiter::Brace, TokenStream::new()); + clause.set_span(span); + + TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("if", Span::call_site())), + TokenTree::Ident(Ident::new("true", Span::call_site())), + TokenTree::Group(clause.clone()), + TokenTree::Ident(Ident::new("else", Span::call_site())), + TokenTree::Group(clause), + ]) + })), + ]) +} diff --git a/src/tools/clippy/tests/ui/crashes/auxiliary/use_self_macro.rs b/src/tools/clippy/tests/ui/crashes/auxiliary/use_self_macro.rs new file mode 100644 index 0000000000000..a8a85b4baefb7 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/auxiliary/use_self_macro.rs @@ -0,0 +1,15 @@ +macro_rules! use_self { + ( + impl $ty:ident { + fn func(&$this:ident) { + [fields($($field:ident)*)] + } + } + ) => ( + impl $ty { + fn func(&$this) { + let $ty { $($field),* } = $this; + } + } + ) +} diff --git a/src/tools/clippy/tests/ui/crashes/cc_seme.rs b/src/tools/clippy/tests/ui/crashes/cc_seme.rs new file mode 100644 index 0000000000000..c48c7e9e6c6ba --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/cc_seme.rs @@ -0,0 +1,29 @@ +// run-pass + +#[allow(dead_code)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/478 + +enum Baz { + One, + Two, +} + +struct Test { + t: Option, + b: Baz, +} + +fn main() {} + +pub fn foo() { + use Baz::*; + let x = Test { t: Some(0), b: One }; + + match x { + Test { t: Some(_), b: One } => unreachable!(), + Test { t: Some(42), b: Two } => unreachable!(), + Test { t: None, .. } => unreachable!(), + Test { .. } => unreachable!(), + } +} diff --git a/src/tools/clippy/tests/ui/crashes/enum-glob-import-crate.rs b/src/tools/clippy/tests/ui/crashes/enum-glob-import-crate.rs new file mode 100644 index 0000000000000..db1fa871afe00 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/enum-glob-import-crate.rs @@ -0,0 +1,8 @@ +// run-pass + +#![deny(clippy::all)] +#![allow(unused_imports)] + +use std::*; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-1588.rs b/src/tools/clippy/tests/ui/crashes/ice-1588.rs new file mode 100644 index 0000000000000..15d0f705b367f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-1588.rs @@ -0,0 +1,15 @@ +// run-pass + +#![allow(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1588 + +fn main() { + match 1 { + 1 => {}, + 2 => { + [0; 1]; + }, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-1782.rs b/src/tools/clippy/tests/ui/crashes/ice-1782.rs new file mode 100644 index 0000000000000..1ca6b6976b38f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-1782.rs @@ -0,0 +1,28 @@ +// run-pass + +#![allow(dead_code, unused_variables)] + +/// Should not trigger an ICE in `SpanlessEq` / `consts::constant` +/// +/// Issue: https://github.com/rust-lang/rust-clippy/issues/1782 +use std::{mem, ptr}; + +fn spanless_eq_ice() { + let txt = "something"; + match txt { + "something" => unsafe { + ptr::write( + ptr::null_mut() as *mut u32, + mem::transmute::<[u8; 4], _>([0, 0, 0, 255]), + ) + }, + _ => unsafe { + ptr::write( + ptr::null_mut() as *mut u32, + mem::transmute::<[u8; 4], _>([13, 246, 24, 255]), + ) + }, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-1969.rs b/src/tools/clippy/tests/ui/crashes/ice-1969.rs new file mode 100644 index 0000000000000..837ec9df31ab1 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-1969.rs @@ -0,0 +1,15 @@ +// run-pass + +#![allow(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1969 + +fn main() {} + +pub trait Convert { + type Action: From<*const f64>; + + fn convert(val: *const f64) -> Self::Action { + val.into() + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2499.rs b/src/tools/clippy/tests/ui/crashes/ice-2499.rs new file mode 100644 index 0000000000000..ffef1631775ee --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2499.rs @@ -0,0 +1,28 @@ +// run-pass + +#![allow(dead_code, clippy::char_lit_as_u8, clippy::needless_bool)] + +/// Should not trigger an ICE in `SpanlessHash` / `consts::constant` +/// +/// Issue: https://github.com/rust-lang/rust-clippy/issues/2499 + +fn f(s: &[u8]) -> bool { + let t = s[0] as char; + + match t { + 'E' | 'W' => {}, + 'T' => { + if s[0..4] != ['0' as u8; 4] { + return false; + } else { + return true; + } + }, + _ => { + return false; + }, + } + true +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2594.rs b/src/tools/clippy/tests/ui/crashes/ice-2594.rs new file mode 100644 index 0000000000000..ac19f1976e912 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2594.rs @@ -0,0 +1,22 @@ +// run-pass + +#![allow(dead_code, unused_variables)] + +/// Should not trigger an ICE in `SpanlessHash` / `consts::constant` +/// +/// Issue: https://github.com/rust-lang/rust-clippy/issues/2594 + +fn spanless_hash_ice() { + let txt = "something"; + let empty_header: [u8; 1] = [1; 1]; + + match txt { + "something" => { + let mut headers = [empty_header; 1]; + }, + "" => (), + _ => (), + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2636.rs b/src/tools/clippy/tests/ui/crashes/ice-2636.rs new file mode 100644 index 0000000000000..e0b58157590ab --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2636.rs @@ -0,0 +1,22 @@ +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +macro_rules! test_hash { + ($foo:expr, $($t:ident => $ord:expr),+ ) => { + use self::Foo::*; + match $foo { + $ ( & $t => $ord, + )* + }; + }; +} + +fn main() { + let a = Foo::A; + test_hash!(&a, A => 0, B => 1, C => 2); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2636.stderr b/src/tools/clippy/tests/ui/crashes/ice-2636.stderr new file mode 100644 index 0000000000000..53799b4fbf1d9 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2636.stderr @@ -0,0 +1,17 @@ +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/ice-2636.rs:12:9 + | +LL | / match $foo { +LL | | $ ( & $t => $ord, +LL | | )* +LL | | }; + | |_________^ +... +LL | test_hash!(&a, A => 0, B => 1, C => 2); + | --------------------------------------- in this macro invocation + | + = note: `-D clippy::match-ref-pats` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-2727.rs b/src/tools/clippy/tests/ui/crashes/ice-2727.rs new file mode 100644 index 0000000000000..d832c2860332d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2727.rs @@ -0,0 +1,9 @@ +// run-pass + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2727 + +pub fn f(new: fn()) { + new(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2760.rs b/src/tools/clippy/tests/ui/crashes/ice-2760.rs new file mode 100644 index 0000000000000..9e5e299c336a5 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2760.rs @@ -0,0 +1,25 @@ +// run-pass + +#![allow( + unused_variables, + clippy::blacklisted_name, + clippy::needless_pass_by_value, + dead_code +)] + +/// This should not compile-fail with: +/// +/// error[E0277]: the trait bound `T: Foo` is not satisfied +// See rust-lang/rust-clippy#2760. + +trait Foo { + type Bar; +} + +struct Baz { + bar: T::Bar, +} + +fn take(baz: Baz) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2774.rs b/src/tools/clippy/tests/ui/crashes/ice-2774.rs new file mode 100644 index 0000000000000..47f8e3b18eeaa --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2774.rs @@ -0,0 +1,29 @@ +// run-pass + +use std::collections::HashSet; + +// See rust-lang/rust-clippy#2774. + +#[derive(Eq, PartialEq, Debug, Hash)] +pub struct Bar { + foo: Foo, +} + +#[derive(Eq, PartialEq, Debug, Hash)] +pub struct Foo {} + +#[allow(clippy::implicit_hasher)] +// This should not cause a "cannot relate bound region" ICE. +pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) { + let mut foos = HashSet::new(); + foos.extend(bars.iter().map(|b| &b.foo)); +} + +#[allow(clippy::implicit_hasher)] +// Also, this should not cause a "cannot relate bound region" ICE. +pub fn add_barfoos_to_foos2(bars: &HashSet<&Bar>) { + let mut foos = HashSet::new(); + foos.extend(bars.iter().map(|b| &b.foo)); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2862.rs b/src/tools/clippy/tests/ui/crashes/ice-2862.rs new file mode 100644 index 0000000000000..47324ce183169 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2862.rs @@ -0,0 +1,18 @@ +// run-pass + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2862 + +pub trait FooMap { + fn map B>(&self, f: F) -> B; +} + +impl FooMap for bool { + fn map B>(&self, f: F) -> B { + f() + } +} + +fn main() { + let a = true; + a.map(|| false); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-2865.rs b/src/tools/clippy/tests/ui/crashes/ice-2865.rs new file mode 100644 index 0000000000000..c4f6c0fed6820 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-2865.rs @@ -0,0 +1,18 @@ +// run-pass + +#[allow(dead_code)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2865 + +struct Ice { + size: String, +} + +impl<'a> From for Ice { + fn from(_: String) -> Self { + let text = || "iceberg".to_string(); + Self { size: text() } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3151.rs b/src/tools/clippy/tests/ui/crashes/ice-3151.rs new file mode 100644 index 0000000000000..ffad2d06b56e2 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3151.rs @@ -0,0 +1,17 @@ +// run-pass + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2865 + +#[derive(Clone)] +pub struct HashMap { + hash_builder: S, + table: RawTable, +} + +#[derive(Clone)] +pub struct RawTable { + size: usize, + val: V, +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3462.rs b/src/tools/clippy/tests/ui/crashes/ice-3462.rs new file mode 100644 index 0000000000000..95c7dff9be36b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3462.rs @@ -0,0 +1,25 @@ +// run-pass + +#![warn(clippy::all)] +#![allow(clippy::blacklisted_name)] +#![allow(unused)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/3462 + +enum Foo { + Bar, + Baz, +} + +fn bar(foo: Foo) { + macro_rules! baz { + () => { + if let Foo::Bar = foo {} + }; + } + + baz!(); + baz!(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-360.rs b/src/tools/clippy/tests/ui/crashes/ice-360.rs new file mode 100644 index 0000000000000..6555c19ca6a26 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-360.rs @@ -0,0 +1,12 @@ +fn main() {} + +fn no_panic(slice: &[T]) { + let mut iter = slice.iter(); + loop { + let _ = match iter.next() { + Some(ele) => ele, + None => break, + }; + loop {} + } +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-360.stderr b/src/tools/clippy/tests/ui/crashes/ice-360.stderr new file mode 100644 index 0000000000000..84e31eaf2e9f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-360.stderr @@ -0,0 +1,24 @@ +error: this loop could be written as a `while let` loop + --> $DIR/ice-360.rs:5:5 + | +LL | / loop { +LL | | let _ = match iter.next() { +LL | | Some(ele) => ele, +LL | | None => break, +LL | | }; +LL | | loop {} +LL | | } + | |_____^ help: try: `while let Some(ele) = iter.next() { .. }` + | + = note: `-D clippy::while-let-loop` implied by `-D warnings` + +error: empty `loop {}` detected. You may want to either use `panic!()` or add `std::thread::sleep(..);` to the loop body. + --> $DIR/ice-360.rs:10:9 + | +LL | loop {} + | ^^^^^^^ + | + = note: `-D clippy::empty-loop` implied by `-D warnings` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/crashes/ice-3717.rs b/src/tools/clippy/tests/ui/crashes/ice-3717.rs new file mode 100644 index 0000000000000..f50714643fd25 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3717.rs @@ -0,0 +1,10 @@ +#![deny(clippy::implicit_hasher)] + +use std::collections::HashSet; + +fn main() {} + +pub fn ice_3717(_: &HashSet) { + let _ = [0u8; 0]; + let _: HashSet = HashSet::new(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3717.stderr b/src/tools/clippy/tests/ui/crashes/ice-3717.stderr new file mode 100644 index 0000000000000..296c95abb96d3 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3717.stderr @@ -0,0 +1,22 @@ +error: parameter of type `HashSet` should be generalized over different hashers + --> $DIR/ice-3717.rs:7:21 + | +LL | pub fn ice_3717(_: &HashSet) { + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/ice-3717.rs:1:9 + | +LL | #![deny(clippy::implicit_hasher)] + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: consider adding a type parameter + | +LL | pub fn ice_3717(_: &HashSet) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | let _: HashSet = HashSet::default(); + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-3741.rs b/src/tools/clippy/tests/ui/crashes/ice-3741.rs new file mode 100644 index 0000000000000..74b9c9c86c81c --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3741.rs @@ -0,0 +1,12 @@ +// aux-build:proc_macro_crash.rs +// run-pass + +#![feature(proc_macro_hygiene)] +#![warn(clippy::suspicious_else_formatting)] + +extern crate proc_macro_crash; +use proc_macro_crash::macro_test; + +fn main() { + macro_test!(2); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3747.rs b/src/tools/clippy/tests/ui/crashes/ice-3747.rs new file mode 100644 index 0000000000000..d0b44ebafeeb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3747.rs @@ -0,0 +1,19 @@ +// run-pass + +/// Test for https://github.com/rust-lang/rust-clippy/issues/3747 + +macro_rules! a { + ( $pub:tt $($attr:tt)* ) => { + $($attr)* $pub fn say_hello() {} + }; +} + +macro_rules! b { + () => { + a! { pub } + }; +} + +b! {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3891.rs b/src/tools/clippy/tests/ui/crashes/ice-3891.rs new file mode 100644 index 0000000000000..05c5134c8457b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3891.rs @@ -0,0 +1,3 @@ +fn main() { + 1x; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-3891.stderr b/src/tools/clippy/tests/ui/crashes/ice-3891.stderr new file mode 100644 index 0000000000000..5a285b0e71492 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-3891.stderr @@ -0,0 +1,10 @@ +error: invalid suffix `x` for integer literal + --> $DIR/ice-3891.rs:2:5 + | +LL | 1x; + | ^^ invalid suffix `x` + | + = help: the suffix must be one of the integral types (`u32`, `isize`, etc) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crashes/ice-4121.rs b/src/tools/clippy/tests/ui/crashes/ice-4121.rs new file mode 100644 index 0000000000000..e1a142fdcb67f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4121.rs @@ -0,0 +1,13 @@ +use std::mem; + +pub struct Foo(A, B); + +impl Foo { + const HOST_SIZE: usize = mem::size_of::(); + + pub fn crash() -> bool { + Self::HOST_SIZE == 0 + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4545.rs b/src/tools/clippy/tests/ui/crashes/ice-4545.rs new file mode 100644 index 0000000000000..d9c9c2096d97c --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4545.rs @@ -0,0 +1,14 @@ +fn repro() { + trait Foo { + type Bar; + } + + #[allow(dead_code)] + struct Baz { + field: T::Bar, + } +} + +fn main() { + repro(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4579.rs b/src/tools/clippy/tests/ui/crashes/ice-4579.rs new file mode 100644 index 0000000000000..2e7e279f847dd --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4579.rs @@ -0,0 +1,13 @@ +#![allow(clippy::single_match)] + +use std::ptr; + +fn main() { + match Some(0_usize) { + Some(_) => { + let s = "012345"; + unsafe { ptr::read(s.as_ptr().offset(1) as *const [u8; 5]) }; + }, + _ => (), + }; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4671.rs b/src/tools/clippy/tests/ui/crashes/ice-4671.rs new file mode 100644 index 0000000000000..64e8e7769412d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4671.rs @@ -0,0 +1,21 @@ +#![warn(clippy::use_self)] + +#[macro_use] +#[path = "auxiliary/use_self_macro.rs"] +mod use_self_macro; + +struct Foo { + a: u32, +} + +use_self! { + impl Foo { + fn func(&self) { + [fields( + a + )] + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4727.rs b/src/tools/clippy/tests/ui/crashes/ice-4727.rs new file mode 100644 index 0000000000000..cdb59caec67ed --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4727.rs @@ -0,0 +1,8 @@ +// run-pass + +#![warn(clippy::use_self)] + +#[path = "auxiliary/ice-4727-aux.rs"] +mod aux; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4760.rs b/src/tools/clippy/tests/ui/crashes/ice-4760.rs new file mode 100644 index 0000000000000..ead67d5ed1b1e --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4760.rs @@ -0,0 +1,10 @@ +// run-pass +const COUNT: usize = 2; +struct Thing; +trait Dummy {} + +const _: () = { + impl Dummy for Thing where [i32; COUNT]: Sized {} +}; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4775.rs b/src/tools/clippy/tests/ui/crashes/ice-4775.rs new file mode 100644 index 0000000000000..31e53e846d54d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4775.rs @@ -0,0 +1,14 @@ +#![feature(const_generics)] +#![allow(incomplete_features)] + +pub struct ArrayWrapper([usize; N]); + +impl ArrayWrapper<{ N }> { + pub fn ice(&self) { + for i in self.0.iter() { + println!("{}", i); + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-4968.rs b/src/tools/clippy/tests/ui/crashes/ice-4968.rs new file mode 100644 index 0000000000000..3822f17459854 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-4968.rs @@ -0,0 +1,20 @@ +// check-pass + +// Test for https://github.com/rust-lang/rust-clippy/issues/4968 + +#![warn(clippy::unsound_collection_transmute)] + +trait Trait { + type Assoc; +} + +use std::mem::{self, ManuallyDrop}; + +#[allow(unused)] +fn func(slice: Vec) { + unsafe { + let _: Vec> = mem::transmute(slice); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5207.rs b/src/tools/clippy/tests/ui/crashes/ice-5207.rs new file mode 100644 index 0000000000000..1b20c9defac82 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5207.rs @@ -0,0 +1,7 @@ +// edition:2018 + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/5207 + +pub async fn bar<'a, T: 'a>(_: T) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5223.rs b/src/tools/clippy/tests/ui/crashes/ice-5223.rs new file mode 100644 index 0000000000000..9bb2e227fc12e --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5223.rs @@ -0,0 +1,18 @@ +// Regression test for #5233 + +#![feature(const_generics)] +#![allow(incomplete_features)] +#![warn(clippy::indexing_slicing, clippy::iter_cloned_collect)] + +pub struct KotomineArray { + arr: [T; N], +} + +impl KotomineArray { + pub fn ice(self) { + let _ = self.arr[..]; + let _ = self.arr.iter().cloned().collect::>(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5238.rs b/src/tools/clippy/tests/ui/crashes/ice-5238.rs new file mode 100644 index 0000000000000..989eb6d448557 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5238.rs @@ -0,0 +1,9 @@ +// Regression test for #5238 / https://github.com/rust-lang/rust/pull/69562 + +#![feature(generators, generator_trait)] + +fn main() { + let _ = || { + yield; + }; +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-5497.rs b/src/tools/clippy/tests/ui/crashes/ice-5497.rs new file mode 100644 index 0000000000000..0769bce5fc809 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-5497.rs @@ -0,0 +1,11 @@ +// reduced from rustc issue-69020-assoc-const-arith-overflow.rs +pub fn main() {} + +pub trait Foo { + const OOB: i32; +} + +impl Foo for Vec { + const OOB: i32 = [1][1] + T::OOB; + //~^ ERROR operation will panic +} diff --git a/src/tools/clippy/tests/ui/crashes/ice-700.rs b/src/tools/clippy/tests/ui/crashes/ice-700.rs new file mode 100644 index 0000000000000..b06df83d51a5b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-700.rs @@ -0,0 +1,11 @@ +// run-pass + +#![deny(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/700 + +fn core() {} + +fn main() { + core(); +} diff --git a/src/tools/clippy/tests/ui/crashes/ice_exacte_size.rs b/src/tools/clippy/tests/ui/crashes/ice_exacte_size.rs new file mode 100644 index 0000000000000..e02eb28ab8650 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice_exacte_size.rs @@ -0,0 +1,21 @@ +// run-pass + +#![deny(clippy::all)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1336 + +#[allow(dead_code)] +struct Foo; + +impl Iterator for Foo { + type Item = (); + + fn next(&mut self) -> Option<()> { + let _ = self.len() == 0; + unimplemented!() + } +} + +impl ExactSizeIterator for Foo {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/if_same_then_else.rs b/src/tools/clippy/tests/ui/crashes/if_same_then_else.rs new file mode 100644 index 0000000000000..4ef992b05e761 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/if_same_then_else.rs @@ -0,0 +1,18 @@ +// run-pass + +#![allow(clippy::comparison_chain)] +#![deny(clippy::if_same_then_else)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2426 + +fn main() {} + +pub fn foo(a: i32, b: i32) -> Option<&'static str> { + if a == b { + None + } else if a > b { + Some("a pfeil b") + } else { + None + } +} diff --git a/src/tools/clippy/tests/ui/crashes/inherent_impl.rs b/src/tools/clippy/tests/ui/crashes/inherent_impl.rs new file mode 100644 index 0000000000000..aeb27b5ba8c2f --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/inherent_impl.rs @@ -0,0 +1,26 @@ +#![deny(clippy::multiple_inherent_impl)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/4578 + +macro_rules! impl_foo { + ($struct:ident) => { + impl $struct { + fn foo() {} + } + }; +} + +macro_rules! impl_bar { + ($struct:ident) => { + impl $struct { + fn bar() {} + } + }; +} + +struct MyStruct; + +impl_foo!(MyStruct); +impl_bar!(MyStruct); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/issue-825.rs b/src/tools/clippy/tests/ui/crashes/issue-825.rs new file mode 100644 index 0000000000000..3d4a88ab3c4e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/issue-825.rs @@ -0,0 +1,27 @@ +// run-pass + +#![allow(warnings)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/825 + +// this should compile in a reasonable amount of time +fn rust_type_id(name: &str) { + if "bool" == &name[..] + || "uint" == &name[..] + || "u8" == &name[..] + || "u16" == &name[..] + || "u32" == &name[..] + || "f32" == &name[..] + || "f64" == &name[..] + || "i8" == &name[..] + || "i16" == &name[..] + || "i32" == &name[..] + || "i64" == &name[..] + || "Self" == &name[..] + || "str" == &name[..] + { + unreachable!(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/issues_loop_mut_cond.rs b/src/tools/clippy/tests/ui/crashes/issues_loop_mut_cond.rs new file mode 100644 index 0000000000000..c4acd5cda1b0a --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/issues_loop_mut_cond.rs @@ -0,0 +1,30 @@ +// run-pass + +#![allow(dead_code)] + +/// Issue: https://github.com/rust-lang/rust-clippy/issues/2596 +pub fn loop_on_block_condition(u: &mut isize) { + while { *u < 0 } { + *u += 1; + } +} + +/// https://github.com/rust-lang/rust-clippy/issues/2584 +fn loop_with_unsafe_condition(ptr: *const u8) { + let mut len = 0; + while unsafe { *ptr.offset(len) } != 0 { + len += 1; + } +} + +/// https://github.com/rust-lang/rust-clippy/issues/2710 +static mut RUNNING: bool = true; +fn loop_on_static_condition() { + unsafe { + while RUNNING { + RUNNING = false; + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/match_same_arms_const.rs b/src/tools/clippy/tests/ui/crashes/match_same_arms_const.rs new file mode 100644 index 0000000000000..848f0ea52ca5e --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/match_same_arms_const.rs @@ -0,0 +1,20 @@ +// run-pass + +#![deny(clippy::match_same_arms)] + +/// Test for https://github.com/rust-lang/rust-clippy/issues/2427 + +const PRICE_OF_SWEETS: u32 = 5; +const PRICE_OF_KINDNESS: u32 = 0; +const PRICE_OF_DRINKS: u32 = 5; + +pub fn price(thing: &str) -> u32 { + match thing { + "rolo" => PRICE_OF_SWEETS, + "advice" => PRICE_OF_KINDNESS, + "juice" => PRICE_OF_DRINKS, + _ => panic!(), + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/mut_mut_macro.rs b/src/tools/clippy/tests/ui/crashes/mut_mut_macro.rs new file mode 100644 index 0000000000000..d8fbaa5414664 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/mut_mut_macro.rs @@ -0,0 +1,36 @@ +// run-pass + +#![deny(clippy::mut_mut, clippy::zero_ptr, clippy::cmp_nan)] +#![allow(dead_code)] + +// FIXME: compiletest + extern crates doesn't work together. To make this test work, it would need +// the following three lines and the lazy_static crate. +// +// #[macro_use] +// extern crate lazy_static; +// use std::collections::HashMap; + +/// ensure that we don't suggest `is_nan` and `is_null` inside constants +/// FIXME: once const fn is stable, suggest these functions again in constants + +const BAA: *const i32 = 0 as *const i32; +static mut BAR: *const i32 = BAA; +static mut FOO: *const i32 = 0 as *const i32; +static mut BUH: bool = 42.0 < f32::NAN; + +#[allow(unused_variables, unused_mut)] +fn main() { + /* + lazy_static! { + static ref MUT_MAP : HashMap = { + let mut m = HashMap::new(); + m.insert(0, "zero"); + m + }; + static ref MUT_COUNT : usize = MUT_MAP.len(); + } + assert_eq!(*MUT_COUNT, 1); + */ + // FIXME: don't lint in array length, requires `check_body` + //let _ = [""; (42.0 < f32::NAN) as usize]; +} diff --git a/src/tools/clippy/tests/ui/crashes/needless_borrow_fp.rs b/src/tools/clippy/tests/ui/crashes/needless_borrow_fp.rs new file mode 100644 index 0000000000000..48507efe1e98b --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_borrow_fp.rs @@ -0,0 +1,9 @@ +// run-pass + +#[deny(clippy::all)] +#[derive(Debug)] +pub enum Error { + Type(&'static str), +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.rs b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.rs new file mode 100644 index 0000000000000..bd1fa4a0b1ef2 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.rs @@ -0,0 +1,22 @@ +// run-pass + +#![deny(clippy::needless_lifetimes)] +#![allow(dead_code)] + +trait Foo {} + +struct Bar {} + +struct Baz<'a> { + bar: &'a Bar, +} + +impl<'a> Foo for Baz<'a> {} + +impl Bar { + fn baz<'a>(&'a self) -> impl Foo + 'a { + Baz { bar: self } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/procedural_macro.rs b/src/tools/clippy/tests/ui/crashes/procedural_macro.rs new file mode 100644 index 0000000000000..f79d9ab6460b8 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/procedural_macro.rs @@ -0,0 +1,13 @@ +// run-pass + +#[macro_use] +extern crate clippy_mini_macro_test; + +#[deny(warnings)] +fn main() { + let x = Foo; + println!("{:?}", x); +} + +#[derive(ClippyMiniMacroTest, Debug)] +struct Foo; diff --git a/src/tools/clippy/tests/ui/crashes/regressions.rs b/src/tools/clippy/tests/ui/crashes/regressions.rs new file mode 100644 index 0000000000000..623ae51f9f08c --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/regressions.rs @@ -0,0 +1,9 @@ +// run-pass + +#![allow(clippy::blacklisted_name)] + +pub fn foo(bar: *const u8) { + println!("{:#p}", bar); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/returns.rs b/src/tools/clippy/tests/ui/crashes/returns.rs new file mode 100644 index 0000000000000..f2153efc38800 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/returns.rs @@ -0,0 +1,25 @@ +// run-pass + +/// Test for https://github.com/rust-lang/rust-clippy/issues/1346 + +#[deny(warnings)] +fn cfg_return() -> i32 { + #[cfg(unix)] + return 1; + #[cfg(not(unix))] + return 2; +} + +#[deny(warnings)] +fn cfg_let_and_return() -> i32 { + #[cfg(unix)] + let x = 1; + #[cfg(not(unix))] + let x = 2; + x +} + +fn main() { + cfg_return(); + cfg_let_and_return(); +} diff --git a/src/tools/clippy/tests/ui/crashes/shadow.rs b/src/tools/clippy/tests/ui/crashes/shadow.rs new file mode 100644 index 0000000000000..843e8ef64dcd9 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/shadow.rs @@ -0,0 +1,6 @@ +fn main() { + let x: [i32; { + let u = 2; + 4 + }] = [2; { 4 }]; +} diff --git a/src/tools/clippy/tests/ui/crashes/single-match-else.rs b/src/tools/clippy/tests/ui/crashes/single-match-else.rs new file mode 100644 index 0000000000000..3a4bbe310ccaa --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/single-match-else.rs @@ -0,0 +1,13 @@ +// run-pass + +#![warn(clippy::single_match_else)] + +//! Test for https://github.com/rust-lang/rust-clippy/issues/1588 + +fn main() { + let n = match (42, 43) { + (42, n) => n, + _ => panic!("typeck error"), + }; + assert_eq!(n, 43); +} diff --git a/src/tools/clippy/tests/ui/crashes/trivial_bounds.rs b/src/tools/clippy/tests/ui/crashes/trivial_bounds.rs new file mode 100644 index 0000000000000..2bb95c18a3912 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/trivial_bounds.rs @@ -0,0 +1,13 @@ +// run-pass + +#![feature(trivial_bounds)] +#![allow(unused, trivial_bounds)] + +fn test_trivial_bounds() +where + i32: Iterator, +{ + for _ in 2i32 {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/used_underscore_binding_macro.rs b/src/tools/clippy/tests/ui/crashes/used_underscore_binding_macro.rs new file mode 100644 index 0000000000000..265017c51d924 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/used_underscore_binding_macro.rs @@ -0,0 +1,21 @@ +// run-pass + +#![allow(clippy::useless_attribute)] //issue #2910 + +#[macro_use] +extern crate serde_derive; + +/// Tests that we do not lint for unused underscores in a `MacroAttribute` +/// expansion +#[deny(clippy::used_underscore_binding)] +#[derive(Deserialize)] +struct MacroAttributesTest { + _foo: u32, +} + +#[test] +fn macro_attributes_test() { + let _ = MacroAttributesTest { _foo: 0 }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/whitelist/clippy.toml b/src/tools/clippy/tests/ui/crashes/whitelist/clippy.toml new file mode 100644 index 0000000000000..9f87de20baff3 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/whitelist/clippy.toml @@ -0,0 +1,3 @@ +# this is ignored by Clippy, but allowed for other tools like clippy-service +[third-party] +clippy-feature = "nightly" diff --git a/src/tools/clippy/tests/ui/crashes/whitelist/conf_whitelisted.rs b/src/tools/clippy/tests/ui/crashes/whitelist/conf_whitelisted.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/whitelist/conf_whitelisted.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.rs new file mode 100644 index 0000000000000..995787c533668 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.rs @@ -0,0 +1,12 @@ +// ignore-macos +// ignore-windows + +#![feature(main)] + +#[warn(clippy::main_recursion)] +#[allow(unconditional_recursion)] +#[main] +fn a() { + println!("Hello, World!"); + a(); +} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.stderr b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.stderr new file mode 100644 index 0000000000000..f52fc949f6c3e --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/entrypoint_recursion.stderr @@ -0,0 +1,11 @@ +error: recursing into entrypoint `a` + --> $DIR/entrypoint_recursion.rs:11:5 + | +LL | a(); + | ^ + | + = note: `-D clippy::main-recursion` implied by `-D warnings` + = help: consider using another function for this recursion + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs new file mode 100644 index 0000000000000..25b1417be9766 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/no_std_main_recursion.rs @@ -0,0 +1,33 @@ +// ignore-macos +// ignore-windows + +#![feature(lang_items, link_args, start, libc)] +#![link_args = "-nostartfiles"] +#![no_std] + +use core::panic::PanicInfo; +use core::sync::atomic::{AtomicUsize, Ordering}; + +static N: AtomicUsize = AtomicUsize::new(0); + +#[warn(clippy::main_recursion)] +#[start] +fn main(argc: isize, argv: *const *const u8) -> isize { + let x = N.load(Ordering::Relaxed); + N.store(x + 1, Ordering::Relaxed); + + if x < 3 { + main(argc, argv); + } + + 0 +} + +#[allow(clippy::empty_loop)] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.rs b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.rs new file mode 100644 index 0000000000000..89ff6609934d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.rs @@ -0,0 +1,6 @@ +#[warn(clippy::main_recursion)] +#[allow(unconditional_recursion)] +fn main() { + println!("Hello, World!"); + main(); +} diff --git a/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.stderr b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.stderr new file mode 100644 index 0000000000000..0a260f9d2309e --- /dev/null +++ b/src/tools/clippy/tests/ui/crate_level_checks/std_main_recursion.stderr @@ -0,0 +1,11 @@ +error: recursing into entrypoint `main` + --> $DIR/std_main_recursion.rs:5:5 + | +LL | main(); + | ^^^^ + | + = note: `-D clippy::main-recursion` implied by `-D warnings` + = help: consider using another function for this recursion + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/cstring.rs b/src/tools/clippy/tests/ui/cstring.rs new file mode 100644 index 0000000000000..6cdd6b4ff6e77 --- /dev/null +++ b/src/tools/clippy/tests/ui/cstring.rs @@ -0,0 +1,24 @@ +#![deny(clippy::temporary_cstring_as_ptr)] + +fn main() {} + +fn temporary_cstring() { + use std::ffi::CString; + + CString::new("foo").unwrap().as_ptr(); + CString::new("foo").expect("dummy").as_ptr(); +} + +mod issue4375 { + use std::ffi::CString; + use std::os::raw::c_char; + + extern "C" { + fn foo(data: *const c_char); + } + + pub fn bar(v: &[u8]) { + let cstr = CString::new(v); + unsafe { foo(cstr.unwrap().as_ptr()) } + } +} diff --git a/src/tools/clippy/tests/ui/cstring.stderr b/src/tools/clippy/tests/ui/cstring.stderr new file mode 100644 index 0000000000000..87cb29be57758 --- /dev/null +++ b/src/tools/clippy/tests/ui/cstring.stderr @@ -0,0 +1,46 @@ +error: you are getting the inner pointer of a temporary `CString` + --> $DIR/cstring.rs:8:5 + | +LL | CString::new("foo").unwrap().as_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/cstring.rs:1:9 + | +LL | #![deny(clippy::temporary_cstring_as_ptr)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: that pointer will be invalid outside this expression +help: assign the `CString` to a variable to extend its lifetime + --> $DIR/cstring.rs:8:5 + | +LL | CString::new("foo").unwrap().as_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you are getting the inner pointer of a temporary `CString` + --> $DIR/cstring.rs:9:5 + | +LL | CString::new("foo").expect("dummy").as_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: that pointer will be invalid outside this expression +help: assign the `CString` to a variable to extend its lifetime + --> $DIR/cstring.rs:9:5 + | +LL | CString::new("foo").expect("dummy").as_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you are getting the inner pointer of a temporary `CString` + --> $DIR/cstring.rs:22:22 + | +LL | unsafe { foo(cstr.unwrap().as_ptr()) } + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: that pointer will be invalid outside this expression +help: assign the `CString` to a variable to extend its lifetime + --> $DIR/cstring.rs:22:22 + | +LL | unsafe { foo(cstr.unwrap().as_ptr()) } + | ^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/custom_ice_message.rs b/src/tools/clippy/tests/ui/custom_ice_message.rs new file mode 100644 index 0000000000000..5b30c9d5721ca --- /dev/null +++ b/src/tools/clippy/tests/ui/custom_ice_message.rs @@ -0,0 +1,10 @@ +// rustc-env:RUST_BACKTRACE=0 +// normalize-stderr-test: "Clippy version: .*" -> "Clippy version: foo" +// normalize-stderr-test: "internal_lints.rs:\d*:\d*" -> "internal_lints.rs" +// normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints" + +#![deny(clippy::internal)] + +fn it_looks_like_you_are_trying_to_kill_clippy() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/custom_ice_message.stderr b/src/tools/clippy/tests/ui/custom_ice_message.stderr new file mode 100644 index 0000000000000..a9a65a38c109d --- /dev/null +++ b/src/tools/clippy/tests/ui/custom_ice_message.stderr @@ -0,0 +1,11 @@ +thread 'rustc' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints.rs +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +error: internal compiler error: unexpected panic + +note: the compiler unexpectedly panicked. this is a bug. + +note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new + +note: Clippy version: foo + diff --git a/src/tools/clippy/tests/ui/dbg_macro.rs b/src/tools/clippy/tests/ui/dbg_macro.rs new file mode 100644 index 0000000000000..d2df7fbd3e84c --- /dev/null +++ b/src/tools/clippy/tests/ui/dbg_macro.rs @@ -0,0 +1,23 @@ +#![warn(clippy::dbg_macro)] + +fn foo(n: u32) -> u32 { + if let Some(n) = dbg!(n.checked_sub(4)) { + n + } else { + n + } +} + +fn factorial(n: u32) -> u32 { + if dbg!(n <= 1) { + dbg!(1) + } else { + dbg!(n * factorial(n - 1)) + } +} + +fn main() { + dbg!(42); + dbg!(dbg!(dbg!(42))); + foo(3) + dbg!(factorial(4)); +} diff --git a/src/tools/clippy/tests/ui/dbg_macro.stderr b/src/tools/clippy/tests/ui/dbg_macro.stderr new file mode 100644 index 0000000000000..b8aafe9667846 --- /dev/null +++ b/src/tools/clippy/tests/ui/dbg_macro.stderr @@ -0,0 +1,80 @@ +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:4:22 + | +LL | if let Some(n) = dbg!(n.checked_sub(4)) { + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::dbg-macro` implied by `-D warnings` +help: ensure to avoid having uses of it in version control + | +LL | if let Some(n) = n.checked_sub(4) { + | ^^^^^^^^^^^^^^^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:12:8 + | +LL | if dbg!(n <= 1) { + | ^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | if n <= 1 { + | ^^^^^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:13:9 + | +LL | dbg!(1) + | ^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | 1 + | + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:15:9 + | +LL | dbg!(n * factorial(n - 1)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | n * factorial(n - 1) + | + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:20:5 + | +LL | dbg!(42); + | ^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | 42; + | ^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:21:5 + | +LL | dbg!(dbg!(dbg!(42))); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | dbg!(dbg!(42)); + | ^^^^^^^^^^^^^^ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:22:14 + | +LL | foo(3) + dbg!(factorial(4)); + | ^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | foo(3) + factorial(4); + | ^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/debug_assert_with_mut_call.rs b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.rs new file mode 100644 index 0000000000000..477a47118d411 --- /dev/null +++ b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.rs @@ -0,0 +1,133 @@ +// compile-flags: --edition=2018 +#![feature(custom_inner_attributes)] +#![rustfmt::skip] +#![warn(clippy::debug_assert_with_mut_call)] +#![allow(clippy::redundant_closure_call)] + +struct S; + +impl S { + fn bool_self_ref(&self) -> bool { false } + fn bool_self_mut(&mut self) -> bool { false } + fn bool_self_ref_arg_ref(&self, _: &u32) -> bool { false } + fn bool_self_ref_arg_mut(&self, _: &mut u32) -> bool { false } + fn bool_self_mut_arg_ref(&mut self, _: &u32) -> bool { false } + fn bool_self_mut_arg_mut(&mut self, _: &mut u32) -> bool { false } + + fn u32_self_ref(&self) -> u32 { 0 } + fn u32_self_mut(&mut self) -> u32 { 0 } + fn u32_self_ref_arg_ref(&self, _: &u32) -> u32 { 0 } + fn u32_self_ref_arg_mut(&self, _: &mut u32) -> u32 { 0 } + fn u32_self_mut_arg_ref(&mut self, _: &u32) -> u32 { 0 } + fn u32_self_mut_arg_mut(&mut self, _: &mut u32) -> u32 { 0 } +} + +fn bool_ref(_: &u32) -> bool { false } +fn bool_mut(_: &mut u32) -> bool { false } +fn u32_ref(_: &u32) -> u32 { 0 } +fn u32_mut(_: &mut u32) -> u32 { 0 } + +fn func_non_mutable() { + debug_assert!(bool_ref(&3)); + debug_assert!(!bool_ref(&3)); + + debug_assert_eq!(0, u32_ref(&3)); + debug_assert_eq!(u32_ref(&3), 0); + + debug_assert_ne!(1, u32_ref(&3)); + debug_assert_ne!(u32_ref(&3), 1); +} + +fn func_mutable() { + debug_assert!(bool_mut(&mut 3)); + debug_assert!(!bool_mut(&mut 3)); + + debug_assert_eq!(0, u32_mut(&mut 3)); + debug_assert_eq!(u32_mut(&mut 3), 0); + + debug_assert_ne!(1, u32_mut(&mut 3)); + debug_assert_ne!(u32_mut(&mut 3), 1); +} + +fn method_non_mutable() { + debug_assert!(S.bool_self_ref()); + debug_assert!(S.bool_self_ref_arg_ref(&3)); + + debug_assert_eq!(S.u32_self_ref(), 0); + debug_assert_eq!(S.u32_self_ref_arg_ref(&3), 0); + + debug_assert_ne!(S.u32_self_ref(), 1); + debug_assert_ne!(S.u32_self_ref_arg_ref(&3), 1); +} + +fn method_mutable() { + debug_assert!(S.bool_self_mut()); + debug_assert!(!S.bool_self_mut()); + debug_assert!(S.bool_self_ref_arg_mut(&mut 3)); + debug_assert!(S.bool_self_mut_arg_ref(&3)); + debug_assert!(S.bool_self_mut_arg_mut(&mut 3)); + + debug_assert_eq!(S.u32_self_mut(), 0); + debug_assert_eq!(S.u32_self_mut_arg_ref(&3), 0); + debug_assert_eq!(S.u32_self_ref_arg_mut(&mut 3), 0); + debug_assert_eq!(S.u32_self_mut_arg_mut(&mut 3), 0); + + debug_assert_ne!(S.u32_self_mut(), 1); + debug_assert_ne!(S.u32_self_mut_arg_ref(&3), 1); + debug_assert_ne!(S.u32_self_ref_arg_mut(&mut 3), 1); + debug_assert_ne!(S.u32_self_mut_arg_mut(&mut 3), 1); +} + +fn misc() { + // with variable + let mut v: Vec = vec![1, 2, 3, 4]; + debug_assert_eq!(v.get(0), Some(&1)); + debug_assert_ne!(v[0], 2); + debug_assert_eq!(v.pop(), Some(1)); + debug_assert_ne!(Some(3), v.pop()); + + let a = &mut 3; + debug_assert!(bool_mut(a)); + + // nested + debug_assert!(!(bool_ref(&u32_mut(&mut 3)))); + + // chained + debug_assert_eq!(v.pop().unwrap(), 3); + + // format args + debug_assert!(bool_ref(&3), "w/o format"); + debug_assert!(bool_mut(&mut 3), "w/o format"); + debug_assert!(bool_ref(&3), "{} format", "w/"); + debug_assert!(bool_mut(&mut 3), "{} format", "w/"); + + // sub block + let mut x = 42_u32; + debug_assert!({ + bool_mut(&mut x); + x > 10 + }); + + // closures + debug_assert!((|| { + let mut x = 42; + bool_mut(&mut x); + x > 10 + })()); +} + +async fn debug_await() { + debug_assert!(async { + true + }.await); +} + +fn main() { + func_non_mutable(); + func_mutable(); + method_non_mutable(); + method_mutable(); + + misc(); + debug_await(); +} diff --git a/src/tools/clippy/tests/ui/debug_assert_with_mut_call.stderr b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.stderr new file mode 100644 index 0000000000000..a2ca71b57a6fd --- /dev/null +++ b/src/tools/clippy/tests/ui/debug_assert_with_mut_call.stderr @@ -0,0 +1,172 @@ +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:42:19 + | +LL | debug_assert!(bool_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::debug-assert-with-mut-call` implied by `-D warnings` + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:43:20 + | +LL | debug_assert!(!bool_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:45:25 + | +LL | debug_assert_eq!(0, u32_mut(&mut 3)); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:46:22 + | +LL | debug_assert_eq!(u32_mut(&mut 3), 0); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:48:25 + | +LL | debug_assert_ne!(1, u32_mut(&mut 3)); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:49:22 + | +LL | debug_assert_ne!(u32_mut(&mut 3), 1); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:64:19 + | +LL | debug_assert!(S.bool_self_mut()); + | ^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:65:20 + | +LL | debug_assert!(!S.bool_self_mut()); + | ^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:66:19 + | +LL | debug_assert!(S.bool_self_ref_arg_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:67:19 + | +LL | debug_assert!(S.bool_self_mut_arg_ref(&3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:68:19 + | +LL | debug_assert!(S.bool_self_mut_arg_mut(&mut 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:70:22 + | +LL | debug_assert_eq!(S.u32_self_mut(), 0); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:71:22 + | +LL | debug_assert_eq!(S.u32_self_mut_arg_ref(&3), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:72:22 + | +LL | debug_assert_eq!(S.u32_self_ref_arg_mut(&mut 3), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:73:22 + | +LL | debug_assert_eq!(S.u32_self_mut_arg_mut(&mut 3), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:75:22 + | +LL | debug_assert_ne!(S.u32_self_mut(), 1); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:76:22 + | +LL | debug_assert_ne!(S.u32_self_mut_arg_ref(&3), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:77:22 + | +LL | debug_assert_ne!(S.u32_self_ref_arg_mut(&mut 3), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:78:22 + | +LL | debug_assert_ne!(S.u32_self_mut_arg_mut(&mut 3), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:86:22 + | +LL | debug_assert_eq!(v.pop(), Some(1)); + | ^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_ne!` + --> $DIR/debug_assert_with_mut_call.rs:87:31 + | +LL | debug_assert_ne!(Some(3), v.pop()); + | ^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:90:19 + | +LL | debug_assert!(bool_mut(a)); + | ^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:93:31 + | +LL | debug_assert!(!(bool_ref(&u32_mut(&mut 3)))); + | ^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert_eq!` + --> $DIR/debug_assert_with_mut_call.rs:96:22 + | +LL | debug_assert_eq!(v.pop().unwrap(), 3); + | ^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:100:19 + | +LL | debug_assert!(bool_mut(&mut 3), "w/o format"); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:102:19 + | +LL | debug_assert!(bool_mut(&mut 3), "{} format", "w/"); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:107:9 + | +LL | bool_mut(&mut x); + | ^^^^^^^^^^^^^^^^ + +error: do not call a function with mutable arguments inside of `debug_assert!` + --> $DIR/debug_assert_with_mut_call.rs:114:9 + | +LL | bool_mut(&mut x); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 28 previous errors + diff --git a/src/tools/clippy/tests/ui/decimal_literal_representation.fixed b/src/tools/clippy/tests/ui/decimal_literal_representation.fixed new file mode 100644 index 0000000000000..de391465125ce --- /dev/null +++ b/src/tools/clippy/tests/ui/decimal_literal_representation.fixed @@ -0,0 +1,27 @@ +// run-rustfix + +#[warn(clippy::decimal_literal_representation)] +#[allow(unused_variables)] +#[rustfmt::skip] +fn main() { + let good = ( // Hex: + 127, // 0x7F + 256, // 0x100 + 511, // 0x1FF + 2048, // 0x800 + 4090, // 0xFFA + 16_371, // 0x3FF3 + 61_683, // 0xF0F3 + 2_131_750_925, // 0x7F0F_F00D + ); + let bad = ( // Hex: + 0x8005, // 0x8005 + 0xFF00, // 0xFF00 + 0x7F0F_F00F, // 0x7F0F_F00F + 0x7FFF_FFFF, // 0x7FFF_FFFF + #[allow(overflowing_literals)] + 0xF0F0_F0F0, // 0xF0F0_F0F0 + 0x8005_usize, // 0x8005_usize + 0x7F0F_F00F_isize, // 0x7F0F_F00F_isize + ); +} diff --git a/src/tools/clippy/tests/ui/decimal_literal_representation.rs b/src/tools/clippy/tests/ui/decimal_literal_representation.rs new file mode 100644 index 0000000000000..55d07698e7e5b --- /dev/null +++ b/src/tools/clippy/tests/ui/decimal_literal_representation.rs @@ -0,0 +1,27 @@ +// run-rustfix + +#[warn(clippy::decimal_literal_representation)] +#[allow(unused_variables)] +#[rustfmt::skip] +fn main() { + let good = ( // Hex: + 127, // 0x7F + 256, // 0x100 + 511, // 0x1FF + 2048, // 0x800 + 4090, // 0xFFA + 16_371, // 0x3FF3 + 61_683, // 0xF0F3 + 2_131_750_925, // 0x7F0F_F00D + ); + let bad = ( // Hex: + 32_773, // 0x8005 + 65_280, // 0xFF00 + 2_131_750_927, // 0x7F0F_F00F + 2_147_483_647, // 0x7FFF_FFFF + #[allow(overflowing_literals)] + 4_042_322_160, // 0xF0F0_F0F0 + 32_773usize, // 0x8005_usize + 2_131_750_927isize, // 0x7F0F_F00F_isize + ); +} diff --git a/src/tools/clippy/tests/ui/decimal_literal_representation.stderr b/src/tools/clippy/tests/ui/decimal_literal_representation.stderr new file mode 100644 index 0000000000000..8d50c8f83db44 --- /dev/null +++ b/src/tools/clippy/tests/ui/decimal_literal_representation.stderr @@ -0,0 +1,46 @@ +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:18:9 + | +LL | 32_773, // 0x8005 + | ^^^^^^ help: consider: `0x8005` + | + = note: `-D clippy::decimal-literal-representation` implied by `-D warnings` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:19:9 + | +LL | 65_280, // 0xFF00 + | ^^^^^^ help: consider: `0xFF00` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:20:9 + | +LL | 2_131_750_927, // 0x7F0F_F00F + | ^^^^^^^^^^^^^ help: consider: `0x7F0F_F00F` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:21:9 + | +LL | 2_147_483_647, // 0x7FFF_FFFF + | ^^^^^^^^^^^^^ help: consider: `0x7FFF_FFFF` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:23:9 + | +LL | 4_042_322_160, // 0xF0F0_F0F0 + | ^^^^^^^^^^^^^ help: consider: `0xF0F0_F0F0` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:24:9 + | +LL | 32_773usize, // 0x8005_usize + | ^^^^^^^^^^^ help: consider: `0x8005_usize` + +error: integer literal has a better hexadecimal representation + --> $DIR/decimal_literal_representation.rs:25:9 + | +LL | 2_131_750_927isize, // 0x7F0F_F00F_isize + | ^^^^^^^^^^^^^^^^^^ help: consider: `0x7F0F_F00F_isize` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const.rs b/src/tools/clippy/tests/ui/declare_interior_mutable_const.rs new file mode 100644 index 0000000000000..b4003ed8932d3 --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const.rs @@ -0,0 +1,93 @@ +#![warn(clippy::declare_interior_mutable_const)] + +use std::borrow::Cow; +use std::cell::Cell; +use std::fmt::Display; +use std::sync::atomic::AtomicUsize; +use std::sync::Once; + +const ATOMIC: AtomicUsize = AtomicUsize::new(5); //~ ERROR interior mutable +const CELL: Cell = Cell::new(6); //~ ERROR interior mutable +const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); +//~^ ERROR interior mutable + +macro_rules! declare_const { + ($name:ident: $ty:ty = $e:expr) => { + const $name: $ty = $e; + }; +} +declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable + +// const ATOMIC_REF: &AtomicUsize = &AtomicUsize::new(7); // This will simply trigger E0492. + +const INTEGER: u8 = 8; +const STRING: String = String::new(); +const STR: &str = "012345"; +const COW: Cow = Cow::Borrowed("abcdef"); +//^ note: a const item of Cow is used in the `postgres` package. + +const NO_ANN: &dyn Display = &70; + +static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); +//^ there should be no lints on this line + +#[allow(clippy::declare_interior_mutable_const)] +const ONCE_INIT: Once = Once::new(); + +trait Trait: Copy { + type NonCopyType; + + const ATOMIC: AtomicUsize; //~ ERROR interior mutable + const INTEGER: u64; + const STRING: String; + const SELF: Self; // (no error) + const INPUT: T; + //~^ ERROR interior mutable + //~| HELP consider requiring `T` to be `Copy` + const ASSOC: Self::NonCopyType; + //~^ ERROR interior mutable + //~| HELP consider requiring `>::NonCopyType` to be `Copy` + + const AN_INPUT: T = Self::INPUT; + //~^ ERROR interior mutable + //~| ERROR consider requiring `T` to be `Copy` + declare_const!(ANOTHER_INPUT: T = Self::INPUT); //~ ERROR interior mutable +} + +trait Trait2 { + type CopyType: Copy; + + const SELF_2: Self; + //~^ ERROR interior mutable + //~| HELP consider requiring `Self` to be `Copy` + const ASSOC_2: Self::CopyType; // (no error) +} + +// we don't lint impl of traits, because an impl has no power to change the interface. +impl Trait for u64 { + type NonCopyType = u16; + + const ATOMIC: AtomicUsize = AtomicUsize::new(9); + const INTEGER: u64 = 10; + const STRING: String = String::new(); + const SELF: Self = 11; + const INPUT: u32 = 12; + const ASSOC: Self::NonCopyType = 13; +} + +struct Local(T, U); + +impl, U: Trait2> Local { + const ASSOC_3: AtomicUsize = AtomicUsize::new(14); //~ ERROR interior mutable + const COW: Cow<'static, str> = Cow::Borrowed("tuvwxy"); + const T_SELF: T = T::SELF_2; + const U_SELF: U = U::SELF_2; + //~^ ERROR interior mutable + //~| HELP consider requiring `U` to be `Copy` + const T_ASSOC: T::NonCopyType = T::ASSOC; + //~^ ERROR interior mutable + //~| HELP consider requiring `>::NonCopyType` to be `Copy` + const U_ASSOC: U::CopyType = U::ASSOC_2; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const.stderr b/src/tools/clippy/tests/ui/declare_interior_mutable_const.stderr new file mode 100644 index 0000000000000..6a9a57361f9f3 --- /dev/null +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const.stderr @@ -0,0 +1,110 @@ +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:9:1 + | +LL | const ATOMIC: AtomicUsize = AtomicUsize::new(5); //~ ERROR interior mutable + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + | + = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:10:1 + | +LL | const CELL: Cell = Cell::new(6); //~ ERROR interior mutable + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:11:1 + | +LL | const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | make this a static item (maybe with lazy_static) + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:16:9 + | +LL | const $name: $ty = $e; + | ^^^^^^^^^^^^^^^^^^^^^^ +... +LL | declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable + | ------------------------------------------ in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:40:5 + | +LL | const ATOMIC: AtomicUsize; //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:44:5 + | +LL | const INPUT: T; + | ^^^^^^^^^^^^^-^ + | | + | consider requiring `T` to be `Copy` + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:47:5 + | +LL | const ASSOC: Self::NonCopyType; + | ^^^^^^^^^^^^^-----------------^ + | | + | consider requiring `>::NonCopyType` to be `Copy` + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:51:5 + | +LL | const AN_INPUT: T = Self::INPUT; + | ^^^^^^^^^^^^^^^^-^^^^^^^^^^^^^^^ + | | + | consider requiring `T` to be `Copy` + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:16:9 + | +LL | const $name: $ty = $e; + | ^^^^^^^^^^^^^^^^^^^^^^ +... +LL | declare_const!(ANOTHER_INPUT: T = Self::INPUT); //~ ERROR interior mutable + | ----------------------------------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:60:5 + | +LL | const SELF_2: Self; + | ^^^^^^^^^^^^^^----^ + | | + | consider requiring `Self` to be `Copy` + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:81:5 + | +LL | const ASSOC_3: AtomicUsize = AtomicUsize::new(14); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:84:5 + | +LL | const U_SELF: U = U::SELF_2; + | ^^^^^^^^^^^^^^-^^^^^^^^^^^^^ + | | + | consider requiring `U` to be `Copy` + +error: a `const` item should never be interior mutable + --> $DIR/declare_interior_mutable_const.rs:87:5 + | +LL | const T_ASSOC: T::NonCopyType = T::ASSOC; + | ^^^^^^^^^^^^^^^--------------^^^^^^^^^^^^ + | | + | consider requiring `>::NonCopyType` to be `Copy` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/def_id_nocore.rs b/src/tools/clippy/tests/ui/def_id_nocore.rs new file mode 100644 index 0000000000000..2a948d60b1089 --- /dev/null +++ b/src/tools/clippy/tests/ui/def_id_nocore.rs @@ -0,0 +1,29 @@ +// ignore-windows +// ignore-macos + +#![feature(no_core, lang_items, start)] +#![no_core] + +#[link(name = "c")] +extern "C" {} + +#[lang = "sized"] +pub trait Sized {} +#[lang = "copy"] +pub trait Copy {} +#[lang = "freeze"] +pub unsafe trait Freeze {} + +#[lang = "start"] +#[start] +fn start(_argc: isize, _argv: *const *const u8) -> isize { + 0 +} + +pub struct A; + +impl A { + pub fn as_ref(self) -> &'static str { + "A" + } +} diff --git a/src/tools/clippy/tests/ui/def_id_nocore.stderr b/src/tools/clippy/tests/ui/def_id_nocore.stderr new file mode 100644 index 0000000000000..ed87a50547d17 --- /dev/null +++ b/src/tools/clippy/tests/ui/def_id_nocore.stderr @@ -0,0 +1,10 @@ +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/def_id_nocore.rs:26:19 + | +LL | pub fn as_ref(self) -> &'static str { + | ^^^^ + | + = note: `-D clippy::wrong-self-convention` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/default_lint.rs b/src/tools/clippy/tests/ui/default_lint.rs new file mode 100644 index 0000000000000..053faae02ce3e --- /dev/null +++ b/src/tools/clippy/tests/ui/default_lint.rs @@ -0,0 +1,27 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +extern crate rustc_lint; + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::TEST_LINT_DEFAULT, + Warn, + "default lint description", + report_in_external_macro: true +} + +declare_lint_pass!(Pass => [TEST_LINT]); +declare_lint_pass!(Pass2 => [TEST_LINT_DEFAULT]); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/default_lint.stderr b/src/tools/clippy/tests/ui/default_lint.stderr new file mode 100644 index 0000000000000..5c5836a7d297e --- /dev/null +++ b/src/tools/clippy/tests/ui/default_lint.stderr @@ -0,0 +1,21 @@ +error: the lint `TEST_LINT_DEFAULT` has the default lint description + --> $DIR/default_lint.rs:17:1 + | +LL | / declare_tool_lint! { +LL | | pub clippy::TEST_LINT_DEFAULT, +LL | | Warn, +LL | | "default lint description", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/default_lint.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::default_lint)]` implied by `#[deny(clippy::internal)]` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/default_trait_access.rs b/src/tools/clippy/tests/ui/default_trait_access.rs new file mode 100644 index 0000000000000..2f1490a70369e --- /dev/null +++ b/src/tools/clippy/tests/ui/default_trait_access.rs @@ -0,0 +1,103 @@ +#![warn(clippy::default_trait_access)] + +use std::default; +use std::default::Default as D2; +use std::string; + +fn main() { + let s1: String = Default::default(); + + let s2 = String::default(); + + let s3: String = D2::default(); + + let s4: String = std::default::Default::default(); + + let s5 = string::String::default(); + + let s6: String = default::Default::default(); + + let s7 = std::string::String::default(); + + let s8: String = DefaultFactory::make_t_badly(); + + let s9: String = DefaultFactory::make_t_nicely(); + + let s10 = DerivedDefault::default(); + + let s11: GenericDerivedDefault = Default::default(); + + let s12 = GenericDerivedDefault::::default(); + + let s13 = TupleDerivedDefault::default(); + + let s14: TupleDerivedDefault = Default::default(); + + let s15: ArrayDerivedDefault = Default::default(); + + let s16 = ArrayDerivedDefault::default(); + + let s17: TupleStructDerivedDefault = Default::default(); + + let s18 = TupleStructDerivedDefault::default(); + + let s19 = ::default(); + + println!( + "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}], [{:?}]", + s1, + s2, + s3, + s4, + s5, + s6, + s7, + s8, + s9, + s10, + s11, + s12, + s13, + s14, + s15, + s16, + s17, + s18, + s19, + ); +} + +struct DefaultFactory; + +impl DefaultFactory { + pub fn make_t_badly() -> T { + Default::default() + } + + pub fn make_t_nicely() -> T { + T::default() + } +} + +#[derive(Debug, Default)] +struct DerivedDefault { + pub s: String, +} + +#[derive(Debug, Default)] +struct GenericDerivedDefault { + pub s: T, +} + +#[derive(Debug, Default)] +struct TupleDerivedDefault { + pub s: (String, String), +} + +#[derive(Debug, Default)] +struct ArrayDerivedDefault { + pub s: [String; 10], +} + +#[derive(Debug, Default)] +struct TupleStructDerivedDefault(String); diff --git a/src/tools/clippy/tests/ui/default_trait_access.stderr b/src/tools/clippy/tests/ui/default_trait_access.stderr new file mode 100644 index 0000000000000..453760c6b9211 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_trait_access.stderr @@ -0,0 +1,52 @@ +error: Calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:8:22 + | +LL | let s1: String = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()` + | + = note: `-D clippy::default-trait-access` implied by `-D warnings` + +error: Calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:12:22 + | +LL | let s3: String = D2::default(); + | ^^^^^^^^^^^^^ help: try: `std::string::String::default()` + +error: Calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:14:22 + | +LL | let s4: String = std::default::Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()` + +error: Calling `std::string::String::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:18:22 + | +LL | let s6: String = default::Default::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()` + +error: Calling `GenericDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:28:46 + | +LL | let s11: GenericDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `GenericDerivedDefault::default()` + +error: Calling `TupleDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:34:36 + | +LL | let s14: TupleDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `TupleDerivedDefault::default()` + +error: Calling `ArrayDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:36:36 + | +LL | let s15: ArrayDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `ArrayDerivedDefault::default()` + +error: Calling `TupleStructDerivedDefault::default()` is more clear than this expression + --> $DIR/default_trait_access.rs:40:42 + | +LL | let s17: TupleStructDerivedDefault = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `TupleStructDerivedDefault::default()` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/deprecated.rs b/src/tools/clippy/tests/ui/deprecated.rs new file mode 100644 index 0000000000000..188a641aa1af2 --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated.rs @@ -0,0 +1,11 @@ +#[warn(clippy::str_to_string)] +#[warn(clippy::string_to_string)] +#[warn(clippy::unstable_as_slice)] +#[warn(clippy::unstable_as_mut_slice)] +#[warn(clippy::misaligned_transmute)] +#[warn(clippy::unused_collect)] +#[warn(clippy::invalid_ref)] +#[warn(clippy::into_iter_on_array)] +#[warn(clippy::unused_label)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/deprecated.stderr b/src/tools/clippy/tests/ui/deprecated.stderr new file mode 100644 index 0000000000000..a4efe3d15a952 --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated.stderr @@ -0,0 +1,64 @@ +error: lint `clippy::str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon` + --> $DIR/deprecated.rs:1:8 + | +LL | #[warn(clippy::str_to_string)] + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + +error: lint `clippy::string_to_string` has been removed: `using `string::to_string` is common even today and specialization will likely happen soon` + --> $DIR/deprecated.rs:2:8 + | +LL | #[warn(clippy::string_to_string)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7` + --> $DIR/deprecated.rs:3:8 + | +LL | #[warn(clippy::unstable_as_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unstable_as_mut_slice` has been removed: ``Vec::as_mut_slice` has been stabilized in 1.7` + --> $DIR/deprecated.rs:4:8 + | +LL | #[warn(clippy::unstable_as_mut_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::misaligned_transmute` has been removed: `this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr` + --> $DIR/deprecated.rs:5:8 + | +LL | #[warn(clippy::misaligned_transmute)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unused_collect` has been removed: ``collect` has been marked as #[must_use] in rustc and that covers all cases of this lint` + --> $DIR/deprecated.rs:6:8 + | +LL | #[warn(clippy::unused_collect)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::invalid_ref` has been removed: `superseded by rustc lint `invalid_value`` + --> $DIR/deprecated.rs:7:8 + | +LL | #[warn(clippy::invalid_ref)] + | ^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::into_iter_on_array` has been removed: `this lint has been uplifted to rustc and is now called `array_into_iter`` + --> $DIR/deprecated.rs:8:8 + | +LL | #[warn(clippy::into_iter_on_array)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unused_label` has been removed: `this lint has been uplifted to rustc and is now called `unused_labels`` + --> $DIR/deprecated.rs:9:8 + | +LL | #[warn(clippy::unused_label)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon` + --> $DIR/deprecated.rs:1:8 + | +LL | #[warn(clippy::str_to_string)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/deprecated_old.rs b/src/tools/clippy/tests/ui/deprecated_old.rs new file mode 100644 index 0000000000000..2e5c5b7ead12c --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated_old.rs @@ -0,0 +1,7 @@ +#[warn(str_to_string)] +#[warn(string_to_string)] +#[warn(unstable_as_slice)] +#[warn(unstable_as_mut_slice)] +#[warn(misaligned_transmute)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/deprecated_old.stderr b/src/tools/clippy/tests/ui/deprecated_old.stderr new file mode 100644 index 0000000000000..ff3e9e8fcf367 --- /dev/null +++ b/src/tools/clippy/tests/ui/deprecated_old.stderr @@ -0,0 +1,40 @@ +error: lint `str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon` + --> $DIR/deprecated_old.rs:1:8 + | +LL | #[warn(str_to_string)] + | ^^^^^^^^^^^^^ + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + +error: lint `string_to_string` has been removed: `using `string::to_string` is common even today and specialization will likely happen soon` + --> $DIR/deprecated_old.rs:2:8 + | +LL | #[warn(string_to_string)] + | ^^^^^^^^^^^^^^^^ + +error: lint `unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7` + --> $DIR/deprecated_old.rs:3:8 + | +LL | #[warn(unstable_as_slice)] + | ^^^^^^^^^^^^^^^^^ + +error: lint `unstable_as_mut_slice` has been removed: ``Vec::as_mut_slice` has been stabilized in 1.7` + --> $DIR/deprecated_old.rs:4:8 + | +LL | #[warn(unstable_as_mut_slice)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: lint `misaligned_transmute` has been removed: `this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr` + --> $DIR/deprecated_old.rs:5:8 + | +LL | #[warn(misaligned_transmute)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: lint `str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon` + --> $DIR/deprecated_old.rs:1:8 + | +LL | #[warn(str_to_string)] + | ^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/deref_addrof.fixed b/src/tools/clippy/tests/ui/deref_addrof.fixed new file mode 100644 index 0000000000000..9e5b51d6d5e6d --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof.fixed @@ -0,0 +1,39 @@ +// run-rustfix + +fn get_number() -> usize { + 10 +} + +fn get_reference(n: &usize) -> &usize { + n +} + +#[allow(clippy::many_single_char_names, clippy::double_parens)] +#[allow(unused_variables, unused_parens)] +#[warn(clippy::deref_addrof)] +fn main() { + let a = 10; + let aref = &a; + + let b = a; + + let b = get_number(); + + let b = *get_reference(&a); + + let bytes: Vec = vec![1, 2, 3, 4]; + let b = bytes[1..2][0]; + + //This produces a suggestion of 'let b = (a);' which + //will trigger the 'unused_parens' lint + let b = (a); + + let b = a; + + #[rustfmt::skip] + let b = a; + + let b = &a; + + let b = *aref; +} diff --git a/src/tools/clippy/tests/ui/deref_addrof.rs b/src/tools/clippy/tests/ui/deref_addrof.rs new file mode 100644 index 0000000000000..5641a73cbc1cb --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof.rs @@ -0,0 +1,39 @@ +// run-rustfix + +fn get_number() -> usize { + 10 +} + +fn get_reference(n: &usize) -> &usize { + n +} + +#[allow(clippy::many_single_char_names, clippy::double_parens)] +#[allow(unused_variables, unused_parens)] +#[warn(clippy::deref_addrof)] +fn main() { + let a = 10; + let aref = &a; + + let b = *&a; + + let b = *&get_number(); + + let b = *get_reference(&a); + + let bytes: Vec = vec![1, 2, 3, 4]; + let b = *&bytes[1..2][0]; + + //This produces a suggestion of 'let b = (a);' which + //will trigger the 'unused_parens' lint + let b = *&(a); + + let b = *(&a); + + #[rustfmt::skip] + let b = *((&a)); + + let b = *&&a; + + let b = **&aref; +} diff --git a/src/tools/clippy/tests/ui/deref_addrof.stderr b/src/tools/clippy/tests/ui/deref_addrof.stderr new file mode 100644 index 0000000000000..bc51719e8a7a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof.stderr @@ -0,0 +1,52 @@ +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:18:13 + | +LL | let b = *&a; + | ^^^ help: try this: `a` + | + = note: `-D clippy::deref-addrof` implied by `-D warnings` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:20:13 + | +LL | let b = *&get_number(); + | ^^^^^^^^^^^^^^ help: try this: `get_number()` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:25:13 + | +LL | let b = *&bytes[1..2][0]; + | ^^^^^^^^^^^^^^^^ help: try this: `bytes[1..2][0]` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:29:13 + | +LL | let b = *&(a); + | ^^^^^ help: try this: `(a)` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:31:13 + | +LL | let b = *(&a); + | ^^^^^ help: try this: `a` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:34:13 + | +LL | let b = *((&a)); + | ^^^^^^^ help: try this: `a` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:36:13 + | +LL | let b = *&&a; + | ^^^^ help: try this: `&a` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof.rs:38:14 + | +LL | let b = **&aref; + | ^^^^^^ help: try this: `aref` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/deref_addrof_double_trigger.rs b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.rs new file mode 100644 index 0000000000000..4531943299cd1 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.rs @@ -0,0 +1,23 @@ +// This test can't work with run-rustfix because it needs two passes of test+fix + +#[warn(clippy::deref_addrof)] +#[allow(unused_variables, unused_mut)] +fn main() { + let a = 10; + + //This produces a suggestion of 'let b = *&a;' which + //will trigger the 'clippy::deref_addrof' lint again + let b = **&&a; + + { + let mut x = 10; + let y = *&mut x; + } + + { + //This produces a suggestion of 'let y = *&mut x' which + //will trigger the 'clippy::deref_addrof' lint again + let mut x = 10; + let y = **&mut &mut x; + } +} diff --git a/src/tools/clippy/tests/ui/deref_addrof_double_trigger.stderr b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.stderr new file mode 100644 index 0000000000000..2c55a4ed6acdb --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof_double_trigger.stderr @@ -0,0 +1,22 @@ +error: immediately dereferencing a reference + --> $DIR/deref_addrof_double_trigger.rs:10:14 + | +LL | let b = **&&a; + | ^^^^ help: try this: `&a` + | + = note: `-D clippy::deref-addrof` implied by `-D warnings` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof_double_trigger.rs:14:17 + | +LL | let y = *&mut x; + | ^^^^^^^ help: try this: `x` + +error: immediately dereferencing a reference + --> $DIR/deref_addrof_double_trigger.rs:21:18 + | +LL | let y = **&mut &mut x; + | ^^^^^^^^^^^^ help: try this: `&mut x` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/deref_addrof_macro.rs b/src/tools/clippy/tests/ui/deref_addrof_macro.rs new file mode 100644 index 0000000000000..dcebd6c6e29c8 --- /dev/null +++ b/src/tools/clippy/tests/ui/deref_addrof_macro.rs @@ -0,0 +1,10 @@ +macro_rules! m { + ($($x:tt),*) => { &[$(($x, stringify!(x)),)*] }; +} + +#[warn(clippy::deref_addrof)] +fn f() -> [(i32, &'static str); 3] { + *m![1, 2, 3] // should be fine +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/dereference.fixed b/src/tools/clippy/tests/ui/dereference.fixed new file mode 100644 index 0000000000000..459ca91b93b9e --- /dev/null +++ b/src/tools/clippy/tests/ui/dereference.fixed @@ -0,0 +1,93 @@ +// run-rustfix + +#![allow(unused_variables, clippy::many_single_char_names, clippy::clone_double_ref)] +#![warn(clippy::explicit_deref_methods)] + +use std::ops::{Deref, DerefMut}; + +fn concat(deref_str: &str) -> String { + format!("{}bar", deref_str) +} + +fn just_return(deref_str: &str) -> &str { + deref_str +} + +struct CustomVec(Vec); +impl Deref for CustomVec { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.0 + } +} + +fn main() { + let a: &mut String = &mut String::from("foo"); + + // these should require linting + + let b: &str = &*a; + + let b: &mut str = &mut *a; + + // both derefs should get linted here + let b: String = format!("{}, {}", &*a, &*a); + + println!("{}", &*a); + + #[allow(clippy::match_single_binding)] + match &*a { + _ => (), + } + + let b: String = concat(&*a); + + let b = &*just_return(a); + + let b: String = concat(&*just_return(a)); + + let b: &str = &*a.deref(); + + let opt_a = Some(a.clone()); + let b = &*opt_a.unwrap(); + + // following should not require linting + + let cv = CustomVec(vec![0, 42]); + let c = cv.deref()[0]; + + let b: &str = &*a.deref(); + + let b: String = a.deref().clone(); + + let b: usize = a.deref_mut().len(); + + let b: &usize = &a.deref().len(); + + let b: &str = &*a; + + let b: &mut str = &mut *a; + + macro_rules! expr_deref { + ($body:expr) => { + $body.deref() + }; + } + let b: &str = expr_deref!(a); + + // The struct does not implement Deref trait + #[derive(Copy, Clone)] + struct NoLint(u32); + impl NoLint { + pub fn deref(self) -> u32 { + self.0 + } + pub fn deref_mut(self) -> u32 { + self.0 + } + } + let no_lint = NoLint(42); + let b = no_lint.deref(); + let b = no_lint.deref_mut(); +} diff --git a/src/tools/clippy/tests/ui/dereference.rs b/src/tools/clippy/tests/ui/dereference.rs new file mode 100644 index 0000000000000..8dc5272e67fa5 --- /dev/null +++ b/src/tools/clippy/tests/ui/dereference.rs @@ -0,0 +1,93 @@ +// run-rustfix + +#![allow(unused_variables, clippy::many_single_char_names, clippy::clone_double_ref)] +#![warn(clippy::explicit_deref_methods)] + +use std::ops::{Deref, DerefMut}; + +fn concat(deref_str: &str) -> String { + format!("{}bar", deref_str) +} + +fn just_return(deref_str: &str) -> &str { + deref_str +} + +struct CustomVec(Vec); +impl Deref for CustomVec { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.0 + } +} + +fn main() { + let a: &mut String = &mut String::from("foo"); + + // these should require linting + + let b: &str = a.deref(); + + let b: &mut str = a.deref_mut(); + + // both derefs should get linted here + let b: String = format!("{}, {}", a.deref(), a.deref()); + + println!("{}", a.deref()); + + #[allow(clippy::match_single_binding)] + match a.deref() { + _ => (), + } + + let b: String = concat(a.deref()); + + let b = just_return(a).deref(); + + let b: String = concat(just_return(a).deref()); + + let b: &str = a.deref().deref(); + + let opt_a = Some(a.clone()); + let b = opt_a.unwrap().deref(); + + // following should not require linting + + let cv = CustomVec(vec![0, 42]); + let c = cv.deref()[0]; + + let b: &str = &*a.deref(); + + let b: String = a.deref().clone(); + + let b: usize = a.deref_mut().len(); + + let b: &usize = &a.deref().len(); + + let b: &str = &*a; + + let b: &mut str = &mut *a; + + macro_rules! expr_deref { + ($body:expr) => { + $body.deref() + }; + } + let b: &str = expr_deref!(a); + + // The struct does not implement Deref trait + #[derive(Copy, Clone)] + struct NoLint(u32); + impl NoLint { + pub fn deref(self) -> u32 { + self.0 + } + pub fn deref_mut(self) -> u32 { + self.0 + } + } + let no_lint = NoLint(42); + let b = no_lint.deref(); + let b = no_lint.deref_mut(); +} diff --git a/src/tools/clippy/tests/ui/dereference.stderr b/src/tools/clippy/tests/ui/dereference.stderr new file mode 100644 index 0000000000000..d26b462a43362 --- /dev/null +++ b/src/tools/clippy/tests/ui/dereference.stderr @@ -0,0 +1,70 @@ +error: explicit deref method call + --> $DIR/dereference.rs:30:19 + | +LL | let b: &str = a.deref(); + | ^^^^^^^^^ help: try this: `&*a` + | + = note: `-D clippy::explicit-deref-methods` implied by `-D warnings` + +error: explicit deref_mut method call + --> $DIR/dereference.rs:32:23 + | +LL | let b: &mut str = a.deref_mut(); + | ^^^^^^^^^^^^^ help: try this: `&mut *a` + +error: explicit deref method call + --> $DIR/dereference.rs:35:39 + | +LL | let b: String = format!("{}, {}", a.deref(), a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:35:50 + | +LL | let b: String = format!("{}, {}", a.deref(), a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:37:20 + | +LL | println!("{}", a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:40:11 + | +LL | match a.deref() { + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:44:28 + | +LL | let b: String = concat(a.deref()); + | ^^^^^^^^^ help: try this: `&*a` + +error: explicit deref method call + --> $DIR/dereference.rs:46:13 + | +LL | let b = just_return(a).deref(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*just_return(a)` + +error: explicit deref method call + --> $DIR/dereference.rs:48:28 + | +LL | let b: String = concat(just_return(a).deref()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*just_return(a)` + +error: explicit deref method call + --> $DIR/dereference.rs:50:19 + | +LL | let b: &str = a.deref().deref(); + | ^^^^^^^^^^^^^^^^^ help: try this: `&*a.deref()` + +error: explicit deref method call + --> $DIR/dereference.rs:53:13 + | +LL | let b = opt_a.unwrap().deref(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*opt_a.unwrap()` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/derive.rs b/src/tools/clippy/tests/ui/derive.rs new file mode 100644 index 0000000000000..8fcb0e8b28d09 --- /dev/null +++ b/src/tools/clippy/tests/ui/derive.rs @@ -0,0 +1,74 @@ +#![feature(untagged_unions)] +#![allow(dead_code)] +#![warn(clippy::expl_impl_clone_on_copy)] + +#[derive(Copy)] +struct Qux; + +impl Clone for Qux { + fn clone(&self) -> Self { + Qux + } +} + +// looks like unions don't support deriving Clone for now +#[derive(Copy)] +union Union { + a: u8, +} + +impl Clone for Union { + fn clone(&self) -> Self { + Union { a: 42 } + } +} + +// See #666 +#[derive(Copy)] +struct Lt<'a> { + a: &'a u8, +} + +impl<'a> Clone for Lt<'a> { + fn clone(&self) -> Self { + unimplemented!() + } +} + +// Ok, `Clone` cannot be derived because of the big array +#[derive(Copy)] +struct BigArray { + a: [u8; 65], +} + +impl Clone for BigArray { + fn clone(&self) -> Self { + unimplemented!() + } +} + +// Ok, function pointers are not always Clone +#[derive(Copy)] +struct FnPtr { + a: fn() -> !, +} + +impl Clone for FnPtr { + fn clone(&self) -> Self { + unimplemented!() + } +} + +// Ok, generics +#[derive(Copy)] +struct Generic { + a: T, +} + +impl Clone for Generic { + fn clone(&self) -> Self { + unimplemented!() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derive.stderr b/src/tools/clippy/tests/ui/derive.stderr new file mode 100644 index 0000000000000..1328a9b31077e --- /dev/null +++ b/src/tools/clippy/tests/ui/derive.stderr @@ -0,0 +1,83 @@ +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:8:1 + | +LL | / impl Clone for Qux { +LL | | fn clone(&self) -> Self { +LL | | Qux +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::expl-impl-clone-on-copy` implied by `-D warnings` +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:8:1 + | +LL | / impl Clone for Qux { +LL | | fn clone(&self) -> Self { +LL | | Qux +LL | | } +LL | | } + | |_^ + +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:32:1 + | +LL | / impl<'a> Clone for Lt<'a> { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + | +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:32:1 + | +LL | / impl<'a> Clone for Lt<'a> { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:44:1 + | +LL | / impl Clone for BigArray { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + | +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:44:1 + | +LL | / impl Clone for BigArray { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + +error: you are implementing `Clone` explicitly on a `Copy` type + --> $DIR/derive.rs:56:1 + | +LL | / impl Clone for FnPtr { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + | +note: consider deriving `Clone` or removing `Copy` + --> $DIR/derive.rs:56:1 + | +LL | / impl Clone for FnPtr { +LL | | fn clone(&self) -> Self { +LL | | unimplemented!() +LL | | } +LL | | } + | |_^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/derive_hash_xor_eq.rs b/src/tools/clippy/tests/ui/derive_hash_xor_eq.rs new file mode 100644 index 0000000000000..10abe22d53e5c --- /dev/null +++ b/src/tools/clippy/tests/ui/derive_hash_xor_eq.rs @@ -0,0 +1,54 @@ +#[derive(PartialEq, Hash)] +struct Foo; + +impl PartialEq for Foo { + fn eq(&self, _: &u64) -> bool { + true + } +} + +#[derive(Hash)] +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Bar) -> bool { + true + } +} + +#[derive(Hash)] +struct Baz; + +impl PartialEq for Baz { + fn eq(&self, _: &Baz) -> bool { + true + } +} + +#[derive(PartialEq)] +struct Bah; + +impl std::hash::Hash for Bah { + fn hash(&self, _: &mut H) {} +} + +#[derive(PartialEq)] +struct Foo2; + +trait Hash {} + +// We don't want to lint on user-defined traits called `Hash` +impl Hash for Foo2 {} + +mod use_hash { + use std::hash::{Hash, Hasher}; + + #[derive(PartialEq)] + struct Foo3; + + impl Hash for Foo3 { + fn hash(&self, _: &mut H) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/derive_hash_xor_eq.stderr b/src/tools/clippy/tests/ui/derive_hash_xor_eq.stderr new file mode 100644 index 0000000000000..2287a548fe46c --- /dev/null +++ b/src/tools/clippy/tests/ui/derive_hash_xor_eq.stderr @@ -0,0 +1,67 @@ +error: you are deriving `Hash` but have implemented `PartialEq` explicitly + --> $DIR/derive_hash_xor_eq.rs:10:10 + | +LL | #[derive(Hash)] + | ^^^^ + | + = note: `#[deny(clippy::derive_hash_xor_eq)]` on by default +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:13:1 + | +LL | / impl PartialEq for Bar { +LL | | fn eq(&self, _: &Bar) -> bool { +LL | | true +LL | | } +LL | | } + | |_^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `Hash` but have implemented `PartialEq` explicitly + --> $DIR/derive_hash_xor_eq.rs:19:10 + | +LL | #[derive(Hash)] + | ^^^^ + | +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:22:1 + | +LL | / impl PartialEq for Baz { +LL | | fn eq(&self, _: &Baz) -> bool { +LL | | true +LL | | } +LL | | } + | |_^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are implementing `Hash` explicitly but have derived `PartialEq` + --> $DIR/derive_hash_xor_eq.rs:31:1 + | +LL | / impl std::hash::Hash for Bah { +LL | | fn hash(&self, _: &mut H) {} +LL | | } + | |_^ + | +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:28:10 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are implementing `Hash` explicitly but have derived `PartialEq` + --> $DIR/derive_hash_xor_eq.rs:49:5 + | +LL | / impl Hash for Foo3 { +LL | | fn hash(&self, _: &mut H) {} +LL | | } + | |_____^ + | +note: `PartialEq` implemented here + --> $DIR/derive_hash_xor_eq.rs:46:14 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/diverging_sub_expression.rs b/src/tools/clippy/tests/ui/diverging_sub_expression.rs new file mode 100644 index 0000000000000..4df241c9fc39b --- /dev/null +++ b/src/tools/clippy/tests/ui/diverging_sub_expression.rs @@ -0,0 +1,42 @@ +#![warn(clippy::diverging_sub_expression)] +#![allow(clippy::match_same_arms, clippy::logic_bug)] + +#[allow(clippy::empty_loop)] +fn diverge() -> ! { + loop {} +} + +struct A; + +impl A { + fn foo(&self) -> ! { + diverge() + } +} + +#[allow(unused_variables, clippy::unnecessary_operation, clippy::short_circuit_statement)] +fn main() { + let b = true; + b || diverge(); + b || A.foo(); +} + +#[allow(dead_code, unused_variables)] +fn foobar() { + loop { + let x = match 5 { + 4 => return, + 5 => continue, + 6 => true || return, + 7 => true || continue, + 8 => break, + 9 => diverge(), + 3 => true || diverge(), + 10 => match 42 { + 99 => return, + _ => true || panic!("boo"), + }, + _ => true || break, + }; + } +} diff --git a/src/tools/clippy/tests/ui/diverging_sub_expression.stderr b/src/tools/clippy/tests/ui/diverging_sub_expression.stderr new file mode 100644 index 0000000000000..170e7d92de4ac --- /dev/null +++ b/src/tools/clippy/tests/ui/diverging_sub_expression.stderr @@ -0,0 +1,40 @@ +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:20:10 + | +LL | b || diverge(); + | ^^^^^^^^^ + | + = note: `-D clippy::diverging-sub-expression` implied by `-D warnings` + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:21:10 + | +LL | b || A.foo(); + | ^^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:30:26 + | +LL | 6 => true || return, + | ^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:31:26 + | +LL | 7 => true || continue, + | ^^^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:34:26 + | +LL | 3 => true || diverge(), + | ^^^^^^^^^ + +error: sub-expression diverges + --> $DIR/diverging_sub_expression.rs:39:26 + | +LL | _ => true || break, + | ^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/dlist.rs b/src/tools/clippy/tests/ui/dlist.rs new file mode 100644 index 0000000000000..2940d2d290110 --- /dev/null +++ b/src/tools/clippy/tests/ui/dlist.rs @@ -0,0 +1,40 @@ +#![feature(associated_type_defaults)] +#![warn(clippy::linkedlist)] +#![allow(dead_code, clippy::needless_pass_by_value)] + +extern crate alloc; +use alloc::collections::linked_list::LinkedList; + +trait Foo { + type Baz = LinkedList; + fn foo(_: LinkedList); + const BAR: Option>; +} + +// Ok, we don’t want to warn for implementations; see issue #605. +impl Foo for LinkedList { + fn foo(_: LinkedList) {} + const BAR: Option> = None; +} + +struct Bar; +impl Bar { + fn foo(_: LinkedList) {} +} + +pub fn test(my_favourite_linked_list: LinkedList) { + println!("{:?}", my_favourite_linked_list) +} + +pub fn test_ret() -> Option> { + unimplemented!(); +} + +pub fn test_local_not_linted() { + let _: LinkedList; +} + +fn main() { + test(LinkedList::new()); + test_local_not_linted(); +} diff --git a/src/tools/clippy/tests/ui/dlist.stderr b/src/tools/clippy/tests/ui/dlist.stderr new file mode 100644 index 0000000000000..64fde33c64f52 --- /dev/null +++ b/src/tools/clippy/tests/ui/dlist.stderr @@ -0,0 +1,51 @@ +error: I see you're using a LinkedList! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:9:16 + | +LL | type Baz = LinkedList; + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::linkedlist` implied by `-D warnings` + = help: a `VecDeque` might work + +error: I see you're using a LinkedList! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:10:15 + | +LL | fn foo(_: LinkedList); + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: I see you're using a LinkedList! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:11:23 + | +LL | const BAR: Option>; + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: I see you're using a LinkedList! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:22:15 + | +LL | fn foo(_: LinkedList) {} + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: I see you're using a LinkedList! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:25:39 + | +LL | pub fn test(my_favourite_linked_list: LinkedList) { + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: I see you're using a LinkedList! Perhaps you meant some other data structure? + --> $DIR/dlist.rs:29:29 + | +LL | pub fn test_ret() -> Option> { + | ^^^^^^^^^^^^^^ + | + = help: a `VecDeque` might work + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/doc.rs b/src/tools/clippy/tests/ui/doc.rs new file mode 100644 index 0000000000000..77620c857e66e --- /dev/null +++ b/src/tools/clippy/tests/ui/doc.rs @@ -0,0 +1,184 @@ +//! This file tests for the `DOC_MARKDOWN` lint. + +#![allow(dead_code)] +#![warn(clippy::doc_markdown)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +/// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) +/// Markdown is _weird_. I mean _really weird_. This \_ is ok. So is `_`. But not Foo::some_fun +/// which should be reported only once despite being __doubly bad__. +/// Here be ::a::global:path. +/// That's not code ~NotInCodeBlock~. +/// be_sure_we_got_to_the_end_of_it +fn foo_bar() { +} + +/// That one tests multiline ticks. +/// ```rust +/// foo_bar FOO_BAR +/// _foo bar_ +/// ``` +/// +/// ~~~rust +/// foo_bar FOO_BAR +/// _foo bar_ +/// ~~~ +/// be_sure_we_got_to_the_end_of_it +fn multiline_codeblock() { +} + +/// This _is a test for +/// multiline +/// emphasis_. +/// be_sure_we_got_to_the_end_of_it +fn test_emphasis() { +} + +/// This tests units. See also #835. +/// kiB MiB GiB TiB PiB EiB +/// kib Mib Gib Tib Pib Eib +/// kB MB GB TB PB EB +/// kb Mb Gb Tb Pb Eb +/// 32kiB 32MiB 32GiB 32TiB 32PiB 32EiB +/// 32kib 32Mib 32Gib 32Tib 32Pib 32Eib +/// 32kB 32MB 32GB 32TB 32PB 32EB +/// 32kb 32Mb 32Gb 32Tb 32Pb 32Eb +/// NaN +/// be_sure_we_got_to_the_end_of_it +fn test_units() { +} + +/// This test has [a link_with_underscores][chunked-example] inside it. See #823. +/// See also [the issue tracker](https://github.com/rust-lang/rust-clippy/search?q=clippy::doc_markdown&type=Issues) +/// on GitHub (which is a camel-cased word, but is OK). And here is another [inline link][inline_link]. +/// It can also be [inline_link2]. +/// +/// [chunked-example]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example +/// [inline_link]: https://foobar +/// [inline_link2]: https://foobar +/// The `main` function is the entry point of the program. Here it only calls the `foo_bar` and +/// `multiline_ticks` functions. +/// +/// expression of the type `_ m c` (where `` +/// is one of {`&`, '|'} and `` is one of {`!=`, `>=`, `>` , +/// be_sure_we_got_to_the_end_of_it +fn main() { + foo_bar(); + multiline_codeblock(); + test_emphasis(); + test_units(); +} + +/// ## CamelCaseThing +/// Talks about `CamelCaseThing`. Titles should be ignored; see issue #897. +/// +/// # CamelCaseThing +/// +/// Not a title #897 CamelCaseThing +/// be_sure_we_got_to_the_end_of_it +fn issue897() { +} + +/// I am confused by brackets? (`x_y`) +/// I am confused by brackets? (foo `x_y`) +/// I am confused by brackets? (`x_y` foo) +/// be_sure_we_got_to_the_end_of_it +fn issue900() { +} + +/// Diesel queries also have a similar problem to [Iterator][iterator], where +/// /// More talking +/// returning them from a function requires exposing the implementation of that +/// function. The [`helper_types`][helper_types] module exists to help with this, +/// but you might want to hide the return type or have it conditionally change. +/// Boxing can achieve both. +/// +/// [iterator]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html +/// [helper_types]: ../helper_types/index.html +/// be_sure_we_got_to_the_end_of_it +fn issue883() { +} + +/// `foo_bar +/// baz_quz` +/// [foo +/// bar](https://doc.rust-lang.org/stable/std/iter/trait.IteratorFooBar.html) +fn multiline() { +} + +/** E.g., serialization of an empty list: FooBar +``` +That's in a code block: `PackedNode` +``` + +And BarQuz too. +be_sure_we_got_to_the_end_of_it +*/ +fn issue1073() { +} + +/** E.g., serialization of an empty list: FooBar +``` +That's in a code block: PackedNode +``` + +And BarQuz too. +be_sure_we_got_to_the_end_of_it +*/ +fn issue1073_alt() { +} + +/// Tests more than three quotes: +/// ```` +/// DoNotWarn +/// ``` +/// StillDont +/// ```` +/// be_sure_we_got_to_the_end_of_it +fn four_quotes() { +} + +/// See [NIST SP 800-56A, revision 2]. +/// +/// [NIST SP 800-56A, revision 2]: +/// https://github.com/rust-lang/rust-clippy/issues/902#issuecomment-261919419 +fn issue_902_comment() {} + +#[cfg_attr(feature = "a", doc = " ```")] +#[cfg_attr(not(feature = "a"), doc = " ```ignore")] +/// fn main() { +/// let s = "localhost:10000".to_string(); +/// println!("{}", s); +/// } +/// ``` +fn issue_1469() {} + +/** + * This is a doc comment that should not be a list + *This would also be an error under a strict common mark interpretation + */ +fn issue_1920() {} + +/// Ok: +/// +/// Not ok: http://www.unicode.org +/// Not ok: https://www.unicode.org +/// Not ok: http://www.unicode.org/ +/// Not ok: http://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels +fn issue_1832() {} + +/// Ok: CamelCase (It should not be surrounded by backticks) +fn issue_2395() {} + +/// An iterator over mycrate::Collection's values. +/// It should not lint a `'static` lifetime in ticks. +fn issue_2210() {} + +/// This should not cause the lint to trigger: +/// #REQ-data-family.lint_partof_exists +fn issue_2343() {} + +/// This should not cause an ICE: +/// __|_ _|__||_| +fn pulldown_cmark_crash() {} diff --git a/src/tools/clippy/tests/ui/doc.stderr b/src/tools/clippy/tests/ui/doc.stderr new file mode 100644 index 0000000000000..ae9bb394cb9ac --- /dev/null +++ b/src/tools/clippy/tests/ui/doc.stderr @@ -0,0 +1,184 @@ +error: you should put `foo_bar` between ticks in the documentation + --> $DIR/doc.rs:8:9 + | +LL | /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) + | ^^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` + +error: you should put `foo::bar` between ticks in the documentation + --> $DIR/doc.rs:8:51 + | +LL | /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there) + | ^^^^^^^^ + +error: you should put `Foo::some_fun` between ticks in the documentation + --> $DIR/doc.rs:9:83 + | +LL | /// Markdown is _weird_. I mean _really weird_. This /_ is ok. So is `_`. But not Foo::some_fun + | ^^^^^^^^^^^^^ + +error: you should put `a::global:path` between ticks in the documentation + --> $DIR/doc.rs:11:15 + | +LL | /// Here be ::a::global:path. + | ^^^^^^^^^^^^^^ + +error: you should put `NotInCodeBlock` between ticks in the documentation + --> $DIR/doc.rs:12:22 + | +LL | /// That's not code ~NotInCodeBlock~. + | ^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:13:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:27:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:34:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:48:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `link_with_underscores` between ticks in the documentation + --> $DIR/doc.rs:52:22 + | +LL | /// This test has [a link_with_underscores][chunked-example] inside it. See #823. + | ^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `inline_link2` between ticks in the documentation + --> $DIR/doc.rs:55:21 + | +LL | /// It can also be [inline_link2]. + | ^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:65:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `CamelCaseThing` between ticks in the documentation + --> $DIR/doc.rs:73:8 + | +LL | /// ## CamelCaseThing + | ^^^^^^^^^^^^^^ + +error: you should put `CamelCaseThing` between ticks in the documentation + --> $DIR/doc.rs:76:7 + | +LL | /// # CamelCaseThing + | ^^^^^^^^^^^^^^ + +error: you should put `CamelCaseThing` between ticks in the documentation + --> $DIR/doc.rs:78:22 + | +LL | /// Not a title #897 CamelCaseThing + | ^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:79:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:86:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:99:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `FooBar` between ticks in the documentation + --> $DIR/doc.rs:110:43 + | +LL | /** E.g., serialization of an empty list: FooBar + | ^^^^^^ + +error: you should put `BarQuz` between ticks in the documentation + --> $DIR/doc.rs:115:5 + | +LL | And BarQuz too. + | ^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:116:1 + | +LL | be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `FooBar` between ticks in the documentation + --> $DIR/doc.rs:121:43 + | +LL | /** E.g., serialization of an empty list: FooBar + | ^^^^^^ + +error: you should put `BarQuz` between ticks in the documentation + --> $DIR/doc.rs:126:5 + | +LL | And BarQuz too. + | ^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:127:1 + | +LL | be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `be_sure_we_got_to_the_end_of_it` between ticks in the documentation + --> $DIR/doc.rs:138:5 + | +LL | /// be_sure_we_got_to_the_end_of_it + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:165:13 + | +LL | /// Not ok: http://www.unicode.org + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:166:13 + | +LL | /// Not ok: https://www.unicode.org + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:167:13 + | +LL | /// Not ok: http://www.unicode.org/ + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> $DIR/doc.rs:168:13 + | +LL | /// Not ok: http://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you should put `mycrate::Collection` between ticks in the documentation + --> $DIR/doc.rs:174:22 + | +LL | /// An iterator over mycrate::Collection's values. + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 30 previous errors + diff --git a/src/tools/clippy/tests/ui/doc_errors.rs b/src/tools/clippy/tests/ui/doc_errors.rs new file mode 100644 index 0000000000000..445fc8d31d77a --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_errors.rs @@ -0,0 +1,103 @@ +// edition:2018 +#![warn(clippy::missing_errors_doc)] + +use std::io; + +pub fn pub_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +pub async fn async_pub_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// This is not sufficiently documented. +pub fn pub_fn_returning_io_result() -> io::Result<()> { + unimplemented!(); +} + +/// This is not sufficiently documented. +pub async fn async_pub_fn_returning_io_result() -> io::Result<()> { + unimplemented!(); +} + +/// # Errors +/// A description of the errors goes here. +pub fn pub_fn_with_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// # Errors +/// A description of the errors goes here. +pub async fn async_pub_fn_with_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// This function doesn't require the documentation because it is private +fn priv_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +/// This function doesn't require the documentation because it is private +async fn async_priv_fn_missing_errors_header() -> Result<(), ()> { + unimplemented!(); +} + +pub struct Struct1; + +impl Struct1 { + /// This is not sufficiently documented. + pub fn pub_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// This is not sufficiently documented. + pub async fn async_pub_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// # Errors + /// A description of the errors goes here. + pub fn pub_method_with_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// # Errors + /// A description of the errors goes here. + pub async fn async_pub_method_with_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// This function doesn't require the documentation because it is private. + fn priv_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + /// This function doesn't require the documentation because it is private. + async fn async_priv_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } +} + +pub trait Trait1 { + /// This is not sufficiently documented. + fn trait_method_missing_errors_header() -> Result<(), ()>; + + /// # Errors + /// A description of the errors goes here. + fn trait_method_with_errors_header() -> Result<(), ()>; +} + +impl Trait1 for Struct1 { + fn trait_method_missing_errors_header() -> Result<(), ()> { + unimplemented!(); + } + + fn trait_method_with_errors_header() -> Result<(), ()> { + unimplemented!(); + } +} + +fn main() -> Result<(), ()> { + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/doc_errors.stderr b/src/tools/clippy/tests/ui/doc_errors.stderr new file mode 100644 index 0000000000000..f44d6693d303b --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_errors.stderr @@ -0,0 +1,58 @@ +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:6:1 + | +LL | / pub fn pub_fn_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_^ + | + = note: `-D clippy::missing-errors-doc` implied by `-D warnings` + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:10:1 + | +LL | / pub async fn async_pub_fn_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:15:1 + | +LL | / pub fn pub_fn_returning_io_result() -> io::Result<()> { +LL | | unimplemented!(); +LL | | } + | |_^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:20:1 + | +LL | / pub async fn async_pub_fn_returning_io_result() -> io::Result<()> { +LL | | unimplemented!(); +LL | | } + | |_^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:50:5 + | +LL | / pub fn pub_method_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:55:5 + | +LL | / pub async fn async_pub_method_missing_errors_header() -> Result<(), ()> { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: docs for function returning `Result` missing `# Errors` section + --> $DIR/doc_errors.rs:84:5 + | +LL | fn trait_method_missing_errors_header() -> Result<(), ()>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/doc_unsafe.rs b/src/tools/clippy/tests/ui/doc_unsafe.rs new file mode 100644 index 0000000000000..484aa72d59a25 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_unsafe.rs @@ -0,0 +1,100 @@ +// aux-build:doc_unsafe_macros.rs + +#[macro_use] +extern crate doc_unsafe_macros; + +/// This is not sufficiently documented +pub unsafe fn destroy_the_planet() { + unimplemented!(); +} + +/// This one is +/// +/// # Safety +/// +/// This function shouldn't be called unless the horsemen are ready +pub unsafe fn apocalypse(universe: &mut ()) { + unimplemented!(); +} + +/// This is a private function, so docs aren't necessary +unsafe fn you_dont_see_me() { + unimplemented!(); +} + +mod private_mod { + pub unsafe fn only_crate_wide_accessible() { + unimplemented!(); + } + + pub unsafe fn republished() { + unimplemented!(); + } +} + +pub use private_mod::republished; + +pub trait UnsafeTrait { + unsafe fn woefully_underdocumented(self); + + /// # Safety + unsafe fn at_least_somewhat_documented(self); +} + +pub struct Struct; + +impl UnsafeTrait for Struct { + unsafe fn woefully_underdocumented(self) { + // all is well + } + + unsafe fn at_least_somewhat_documented(self) { + // all is still well + } +} + +impl Struct { + pub unsafe fn more_undocumented_unsafe() -> Self { + unimplemented!(); + } + + /// # Safety + pub unsafe fn somewhat_documented(&self) { + unimplemented!(); + } + + unsafe fn private(&self) { + unimplemented!(); + } +} + +macro_rules! very_unsafe { + () => { + pub unsafe fn whee() { + unimplemented!() + } + + /// # Safety + /// + /// Please keep the seat belt fastened + pub unsafe fn drive() { + whee() + } + }; +} + +very_unsafe!(); + +// we don't lint code from external macros +undocd_unsafe!(); + +fn main() { + unsafe { + you_dont_see_me(); + destroy_the_planet(); + let mut universe = (); + apocalypse(&mut universe); + private_mod::only_crate_wide_accessible(); + drive(); + } +} diff --git a/src/tools/clippy/tests/ui/doc_unsafe.stderr b/src/tools/clippy/tests/ui/doc_unsafe.stderr new file mode 100644 index 0000000000000..c784d41ba1724 --- /dev/null +++ b/src/tools/clippy/tests/ui/doc_unsafe.stderr @@ -0,0 +1,47 @@ +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:7:1 + | +LL | / pub unsafe fn destroy_the_planet() { +LL | | unimplemented!(); +LL | | } + | |_^ + | + = note: `-D clippy::missing-safety-doc` implied by `-D warnings` + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:30:5 + | +LL | / pub unsafe fn republished() { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:38:5 + | +LL | unsafe fn woefully_underdocumented(self); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:57:5 + | +LL | / pub unsafe fn more_undocumented_unsafe() -> Self { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: unsafe function's docs miss `# Safety` section + --> $DIR/doc_unsafe.rs:73:9 + | +LL | / pub unsafe fn whee() { +LL | | unimplemented!() +LL | | } + | |_________^ +... +LL | very_unsafe!(); + | --------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/double_comparison.fixed b/src/tools/clippy/tests/ui/double_comparison.fixed new file mode 100644 index 0000000000000..bb6cdaa667d4f --- /dev/null +++ b/src/tools/clippy/tests/ui/double_comparison.fixed @@ -0,0 +1,30 @@ +// run-rustfix + +fn main() { + let x = 1; + let y = 2; + if x <= y { + // do something + } + if x <= y { + // do something + } + if x >= y { + // do something + } + if x >= y { + // do something + } + if x != y { + // do something + } + if x != y { + // do something + } + if x == y { + // do something + } + if x == y { + // do something + } +} diff --git a/src/tools/clippy/tests/ui/double_comparison.rs b/src/tools/clippy/tests/ui/double_comparison.rs new file mode 100644 index 0000000000000..9a2a9068a28de --- /dev/null +++ b/src/tools/clippy/tests/ui/double_comparison.rs @@ -0,0 +1,30 @@ +// run-rustfix + +fn main() { + let x = 1; + let y = 2; + if x == y || x < y { + // do something + } + if x < y || x == y { + // do something + } + if x == y || x > y { + // do something + } + if x > y || x == y { + // do something + } + if x < y || x > y { + // do something + } + if x > y || x < y { + // do something + } + if x <= y && x >= y { + // do something + } + if x >= y && x <= y { + // do something + } +} diff --git a/src/tools/clippy/tests/ui/double_comparison.stderr b/src/tools/clippy/tests/ui/double_comparison.stderr new file mode 100644 index 0000000000000..5dcda7b3af4ac --- /dev/null +++ b/src/tools/clippy/tests/ui/double_comparison.stderr @@ -0,0 +1,52 @@ +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:6:8 + | +LL | if x == y || x < y { + | ^^^^^^^^^^^^^^^ help: try: `x <= y` + | + = note: `-D clippy::double-comparisons` implied by `-D warnings` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:9:8 + | +LL | if x < y || x == y { + | ^^^^^^^^^^^^^^^ help: try: `x <= y` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:12:8 + | +LL | if x == y || x > y { + | ^^^^^^^^^^^^^^^ help: try: `x >= y` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:15:8 + | +LL | if x > y || x == y { + | ^^^^^^^^^^^^^^^ help: try: `x >= y` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:18:8 + | +LL | if x < y || x > y { + | ^^^^^^^^^^^^^^ help: try: `x != y` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:21:8 + | +LL | if x > y || x < y { + | ^^^^^^^^^^^^^^ help: try: `x != y` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:24:8 + | +LL | if x <= y && x >= y { + | ^^^^^^^^^^^^^^^^ help: try: `x == y` + +error: This binary expression can be simplified + --> $DIR/double_comparison.rs:27:8 + | +LL | if x >= y && x <= y { + | ^^^^^^^^^^^^^^^^ help: try: `x == y` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/double_must_use.rs b/src/tools/clippy/tests/ui/double_must_use.rs new file mode 100644 index 0000000000000..a48e675e4ea23 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_must_use.rs @@ -0,0 +1,27 @@ +#![warn(clippy::double_must_use)] + +#[must_use] +pub fn must_use_result() -> Result<(), ()> { + unimplemented!(); +} + +#[must_use] +pub fn must_use_tuple() -> (Result<(), ()>, u8) { + unimplemented!(); +} + +#[must_use] +pub fn must_use_array() -> [Result<(), ()>; 1] { + unimplemented!(); +} + +#[must_use = "With note"] +pub fn must_use_with_note() -> Result<(), ()> { + unimplemented!(); +} + +fn main() { + must_use_result(); + must_use_tuple(); + must_use_with_note(); +} diff --git a/src/tools/clippy/tests/ui/double_must_use.stderr b/src/tools/clippy/tests/ui/double_must_use.stderr new file mode 100644 index 0000000000000..bc37785294fca --- /dev/null +++ b/src/tools/clippy/tests/ui/double_must_use.stderr @@ -0,0 +1,27 @@ +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:4:1 + | +LL | pub fn must_use_result() -> Result<(), ()> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::double-must-use` implied by `-D warnings` + = help: either add some descriptive text or remove the attribute + +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:9:1 + | +LL | pub fn must_use_tuple() -> (Result<(), ()>, u8) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: either add some descriptive text or remove the attribute + +error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]` + --> $DIR/double_must_use.rs:14:1 + | +LL | pub fn must_use_array() -> [Result<(), ()>; 1] { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: either add some descriptive text or remove the attribute + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/double_neg.rs b/src/tools/clippy/tests/ui/double_neg.rs new file mode 100644 index 0000000000000..d47dfcb5ba1ea --- /dev/null +++ b/src/tools/clippy/tests/ui/double_neg.rs @@ -0,0 +1,7 @@ +#[warn(clippy::double_neg)] +fn main() { + let x = 1; + -x; + -(-x); + --x; +} diff --git a/src/tools/clippy/tests/ui/double_neg.stderr b/src/tools/clippy/tests/ui/double_neg.stderr new file mode 100644 index 0000000000000..d82ed05f0543d --- /dev/null +++ b/src/tools/clippy/tests/ui/double_neg.stderr @@ -0,0 +1,10 @@ +error: `--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op + --> $DIR/double_neg.rs:6:5 + | +LL | --x; + | ^^^ + | + = note: `-D clippy::double-neg` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/double_parens.rs b/src/tools/clippy/tests/ui/double_parens.rs new file mode 100644 index 0000000000000..9c7590c7dd632 --- /dev/null +++ b/src/tools/clippy/tests/ui/double_parens.rs @@ -0,0 +1,56 @@ +#![warn(clippy::double_parens)] +#![allow(dead_code)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +fn dummy_fn(_: T) {} + +struct DummyStruct; + +impl DummyStruct { + fn dummy_method(self, _: T) {} +} + +fn simple_double_parens() -> i32 { + ((0)) +} + +fn fn_double_parens() { + dummy_fn((0)); +} + +fn method_double_parens(x: DummyStruct) { + x.dummy_method((0)); +} + +fn tuple_double_parens() -> (i32, i32) { + ((1, 2)) +} + +fn unit_double_parens() { + (()) +} + +fn fn_tuple_ok() { + dummy_fn((1, 2)); +} + +fn method_tuple_ok(x: DummyStruct) { + x.dummy_method((1, 2)); +} + +fn fn_unit_ok() { + dummy_fn(()); +} + +fn method_unit_ok(x: DummyStruct) { + x.dummy_method(()); +} + +// Issue #3206 +fn inside_macro() { + assert_eq!((1, 2), (1, 2), "Error"); + assert_eq!(((1, 2)), (1, 2), "Error"); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/double_parens.stderr b/src/tools/clippy/tests/ui/double_parens.stderr new file mode 100644 index 0000000000000..0e4c9b5682dfb --- /dev/null +++ b/src/tools/clippy/tests/ui/double_parens.stderr @@ -0,0 +1,40 @@ +error: Consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:15:5 + | +LL | ((0)) + | ^^^^^ + | + = note: `-D clippy::double-parens` implied by `-D warnings` + +error: Consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:19:14 + | +LL | dummy_fn((0)); + | ^^^ + +error: Consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:23:20 + | +LL | x.dummy_method((0)); + | ^^^ + +error: Consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:27:5 + | +LL | ((1, 2)) + | ^^^^^^^^ + +error: Consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:31:5 + | +LL | (()) + | ^^^^ + +error: Consider removing unnecessary double parentheses + --> $DIR/double_parens.rs:53:16 + | +LL | assert_eq!(((1, 2)), (1, 2), "Error"); + | ^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/drop_bounds.rs b/src/tools/clippy/tests/ui/drop_bounds.rs new file mode 100644 index 0000000000000..6d6a9dc078399 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_bounds.rs @@ -0,0 +1,8 @@ +#![allow(unused)] +fn foo() {} +fn bar() +where + T: Drop, +{ +} +fn main() {} diff --git a/src/tools/clippy/tests/ui/drop_bounds.stderr b/src/tools/clippy/tests/ui/drop_bounds.stderr new file mode 100644 index 0000000000000..5d360ef30a1d8 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_bounds.stderr @@ -0,0 +1,16 @@ +error: Bounds of the form `T: Drop` are useless. Use `std::mem::needs_drop` to detect if a type has drop glue. + --> $DIR/drop_bounds.rs:2:11 + | +LL | fn foo() {} + | ^^^^ + | + = note: `#[deny(clippy::drop_bounds)]` on by default + +error: Bounds of the form `T: Drop` are useless. Use `std::mem::needs_drop` to detect if a type has drop glue. + --> $DIR/drop_bounds.rs:5:8 + | +LL | T: Drop, + | ^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/drop_forget_copy.rs b/src/tools/clippy/tests/ui/drop_forget_copy.rs new file mode 100644 index 0000000000000..9ddd6d64701a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_forget_copy.rs @@ -0,0 +1,66 @@ +#![warn(clippy::drop_copy, clippy::forget_copy)] +#![allow(clippy::toplevel_ref_arg, clippy::drop_ref, clippy::forget_ref, unused_mut)] + +use std::mem::{drop, forget}; +use std::vec::Vec; + +#[derive(Copy, Clone)] +struct SomeStruct {} + +struct AnotherStruct { + x: u8, + y: u8, + z: Vec, +} + +impl Clone for AnotherStruct { + fn clone(&self) -> AnotherStruct { + AnotherStruct { + x: self.x, + y: self.y, + z: self.z.clone(), + } + } +} + +fn main() { + let s1 = SomeStruct {}; + let s2 = s1; + let s3 = &s1; + let mut s4 = s1; + let ref s5 = s1; + + drop(s1); + drop(s2); + drop(s3); + drop(s4); + drop(s5); + + forget(s1); + forget(s2); + forget(s3); + forget(s4); + forget(s5); + + let a1 = AnotherStruct { + x: 255, + y: 0, + z: vec![1, 2, 3], + }; + let a2 = &a1; + let mut a3 = a1.clone(); + let ref a4 = a1; + let a5 = a1.clone(); + + drop(a2); + drop(a3); + drop(a4); + drop(a5); + + forget(a2); + let a3 = &a1; + forget(a3); + forget(a4); + let a5 = a1.clone(); + forget(a5); +} diff --git a/src/tools/clippy/tests/ui/drop_forget_copy.stderr b/src/tools/clippy/tests/ui/drop_forget_copy.stderr new file mode 100644 index 0000000000000..82a4f047ba858 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_forget_copy.stderr @@ -0,0 +1,76 @@ +error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact. + --> $DIR/drop_forget_copy.rs:33:5 + | +LL | drop(s1); + | ^^^^^^^^ + | + = note: `-D clippy::drop-copy` implied by `-D warnings` +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:33:10 + | +LL | drop(s1); + | ^^ + +error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact. + --> $DIR/drop_forget_copy.rs:34:5 + | +LL | drop(s2); + | ^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:34:10 + | +LL | drop(s2); + | ^^ + +error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact. + --> $DIR/drop_forget_copy.rs:36:5 + | +LL | drop(s4); + | ^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:36:10 + | +LL | drop(s4); + | ^^ + +error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact. + --> $DIR/drop_forget_copy.rs:39:5 + | +LL | forget(s1); + | ^^^^^^^^^^ + | + = note: `-D clippy::forget-copy` implied by `-D warnings` +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:39:12 + | +LL | forget(s1); + | ^^ + +error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact. + --> $DIR/drop_forget_copy.rs:40:5 + | +LL | forget(s2); + | ^^^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:40:12 + | +LL | forget(s2); + | ^^ + +error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact. + --> $DIR/drop_forget_copy.rs:42:5 + | +LL | forget(s4); + | ^^^^^^^^^^ + | +note: argument has type SomeStruct + --> $DIR/drop_forget_copy.rs:42:12 + | +LL | forget(s4); + | ^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/drop_ref.rs b/src/tools/clippy/tests/ui/drop_ref.rs new file mode 100644 index 0000000000000..9181d789d4fb1 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_ref.rs @@ -0,0 +1,72 @@ +#![warn(clippy::drop_ref)] +#![allow(clippy::toplevel_ref_arg)] + +use std::mem::drop; + +struct SomeStruct; + +fn main() { + drop(&SomeStruct); + + let mut owned1 = SomeStruct; + drop(&owned1); + drop(&&owned1); + drop(&mut owned1); + drop(owned1); //OK + + let reference1 = &SomeStruct; + drop(reference1); + + let reference2 = &mut SomeStruct; + drop(reference2); + + let ref reference3 = SomeStruct; + drop(reference3); +} + +#[allow(dead_code)] +fn test_generic_fn_drop(val: T) { + drop(&val); + drop(val); //OK +} + +#[allow(dead_code)] +fn test_similarly_named_function() { + fn drop(_val: T) {} + drop(&SomeStruct); //OK; call to unrelated function which happens to have the same name + std::mem::drop(&SomeStruct); +} + +#[derive(Copy, Clone)] +pub struct Error; +fn produce_half_owl_error() -> Result<(), Error> { + Ok(()) +} + +fn produce_half_owl_ok() -> Result { + Ok(true) +} + +#[allow(dead_code)] +fn test_owl_result() -> Result<(), ()> { + produce_half_owl_error().map_err(|_| ())?; + produce_half_owl_ok().map(|_| ())?; + // the following should not be linted, + // we should not force users to use toilet closures + // to produce owl results when drop is more convenient + produce_half_owl_error().map_err(drop)?; + produce_half_owl_ok().map_err(drop)?; + Ok(()) +} + +#[allow(dead_code)] +fn test_owl_result_2() -> Result { + produce_half_owl_error().map_err(|_| ())?; + produce_half_owl_ok().map(|_| ())?; + // the following should not be linted, + // we should not force users to use toilet closures + // to produce owl results when drop is more convenient + produce_half_owl_error().map_err(drop)?; + produce_half_owl_ok().map(drop)?; + Ok(1) +} diff --git a/src/tools/clippy/tests/ui/drop_ref.stderr b/src/tools/clippy/tests/ui/drop_ref.stderr new file mode 100644 index 0000000000000..35ae88b78a4c5 --- /dev/null +++ b/src/tools/clippy/tests/ui/drop_ref.stderr @@ -0,0 +1,111 @@ +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:9:5 + | +LL | drop(&SomeStruct); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::drop-ref` implied by `-D warnings` +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:9:10 + | +LL | drop(&SomeStruct); + | ^^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:12:5 + | +LL | drop(&owned1); + | ^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:12:10 + | +LL | drop(&owned1); + | ^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:13:5 + | +LL | drop(&&owned1); + | ^^^^^^^^^^^^^^ + | +note: argument has type `&&SomeStruct` + --> $DIR/drop_ref.rs:13:10 + | +LL | drop(&&owned1); + | ^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:14:5 + | +LL | drop(&mut owned1); + | ^^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/drop_ref.rs:14:10 + | +LL | drop(&mut owned1); + | ^^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:18:5 + | +LL | drop(reference1); + | ^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:18:10 + | +LL | drop(reference1); + | ^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:21:5 + | +LL | drop(reference2); + | ^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/drop_ref.rs:21:10 + | +LL | drop(reference2); + | ^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:24:5 + | +LL | drop(reference3); + | ^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:24:10 + | +LL | drop(reference3); + | ^^^^^^^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:29:5 + | +LL | drop(&val); + | ^^^^^^^^^^ + | +note: argument has type `&T` + --> $DIR/drop_ref.rs:29:10 + | +LL | drop(&val); + | ^^^^ + +error: calls to `std::mem::drop` with a reference instead of an owned value. Dropping a reference does nothing. + --> $DIR/drop_ref.rs:37:5 + | +LL | std::mem::drop(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/drop_ref.rs:37:20 + | +LL | std::mem::drop(&SomeStruct); + | ^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/duplicate_underscore_argument.rs b/src/tools/clippy/tests/ui/duplicate_underscore_argument.rs new file mode 100644 index 0000000000000..54d748c7ce280 --- /dev/null +++ b/src/tools/clippy/tests/ui/duplicate_underscore_argument.rs @@ -0,0 +1,10 @@ +#![warn(clippy::duplicate_underscore_argument)] +#[allow(dead_code, unused)] + +fn join_the_dark_side(darth: i32, _darth: i32) {} +fn join_the_light_side(knight: i32, _master: i32) {} // the Force is strong with this one + +fn main() { + join_the_dark_side(0, 0); + join_the_light_side(0, 0); +} diff --git a/src/tools/clippy/tests/ui/duplicate_underscore_argument.stderr b/src/tools/clippy/tests/ui/duplicate_underscore_argument.stderr new file mode 100644 index 0000000000000..f71614a5fd16e --- /dev/null +++ b/src/tools/clippy/tests/ui/duplicate_underscore_argument.stderr @@ -0,0 +1,10 @@ +error: `darth` already exists, having another argument having almost the same name makes code comprehension and documentation more difficult + --> $DIR/duplicate_underscore_argument.rs:4:23 + | +LL | fn join_the_dark_side(darth: i32, _darth: i32) {} + | ^^^^^ + | + = note: `-D clippy::duplicate-underscore-argument` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/duration_subsec.fixed b/src/tools/clippy/tests/ui/duration_subsec.fixed new file mode 100644 index 0000000000000..ee5c7863effcb --- /dev/null +++ b/src/tools/clippy/tests/ui/duration_subsec.fixed @@ -0,0 +1,29 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::duration_subsec)] + +use std::time::Duration; + +fn main() { + let dur = Duration::new(5, 0); + + let bad_millis_1 = dur.subsec_millis(); + let bad_millis_2 = dur.subsec_millis(); + let good_millis = dur.subsec_millis(); + assert_eq!(bad_millis_1, good_millis); + assert_eq!(bad_millis_2, good_millis); + + let bad_micros = dur.subsec_micros(); + let good_micros = dur.subsec_micros(); + assert_eq!(bad_micros, good_micros); + + // Handle refs + let _ = (&dur).subsec_micros(); + + // Handle constants + const NANOS_IN_MICRO: u32 = 1_000; + let _ = dur.subsec_micros(); + + // Other literals aren't linted + let _ = dur.subsec_nanos() / 699; +} diff --git a/src/tools/clippy/tests/ui/duration_subsec.rs b/src/tools/clippy/tests/ui/duration_subsec.rs new file mode 100644 index 0000000000000..3c9d2a2862110 --- /dev/null +++ b/src/tools/clippy/tests/ui/duration_subsec.rs @@ -0,0 +1,29 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::duration_subsec)] + +use std::time::Duration; + +fn main() { + let dur = Duration::new(5, 0); + + let bad_millis_1 = dur.subsec_micros() / 1_000; + let bad_millis_2 = dur.subsec_nanos() / 1_000_000; + let good_millis = dur.subsec_millis(); + assert_eq!(bad_millis_1, good_millis); + assert_eq!(bad_millis_2, good_millis); + + let bad_micros = dur.subsec_nanos() / 1_000; + let good_micros = dur.subsec_micros(); + assert_eq!(bad_micros, good_micros); + + // Handle refs + let _ = (&dur).subsec_nanos() / 1_000; + + // Handle constants + const NANOS_IN_MICRO: u32 = 1_000; + let _ = dur.subsec_nanos() / NANOS_IN_MICRO; + + // Other literals aren't linted + let _ = dur.subsec_nanos() / 699; +} diff --git a/src/tools/clippy/tests/ui/duration_subsec.stderr b/src/tools/clippy/tests/ui/duration_subsec.stderr new file mode 100644 index 0000000000000..bd8adc2c57055 --- /dev/null +++ b/src/tools/clippy/tests/ui/duration_subsec.stderr @@ -0,0 +1,34 @@ +error: Calling `subsec_millis()` is more concise than this calculation + --> $DIR/duration_subsec.rs:10:24 + | +LL | let bad_millis_1 = dur.subsec_micros() / 1_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_millis()` + | + = note: `-D clippy::duration-subsec` implied by `-D warnings` + +error: Calling `subsec_millis()` is more concise than this calculation + --> $DIR/duration_subsec.rs:11:24 + | +LL | let bad_millis_2 = dur.subsec_nanos() / 1_000_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_millis()` + +error: Calling `subsec_micros()` is more concise than this calculation + --> $DIR/duration_subsec.rs:16:22 + | +LL | let bad_micros = dur.subsec_nanos() / 1_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()` + +error: Calling `subsec_micros()` is more concise than this calculation + --> $DIR/duration_subsec.rs:21:13 + | +LL | let _ = (&dur).subsec_nanos() / 1_000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&dur).subsec_micros()` + +error: Calling `subsec_micros()` is more concise than this calculation + --> $DIR/duration_subsec.rs:25:13 + | +LL | let _ = dur.subsec_nanos() / NANOS_IN_MICRO; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/else_if_without_else.rs b/src/tools/clippy/tests/ui/else_if_without_else.rs new file mode 100644 index 0000000000000..879b3ac398e45 --- /dev/null +++ b/src/tools/clippy/tests/ui/else_if_without_else.rs @@ -0,0 +1,58 @@ +#![warn(clippy::all)] +#![warn(clippy::else_if_without_else)] + +fn bla1() -> bool { + unimplemented!() +} +fn bla2() -> bool { + unimplemented!() +} +fn bla3() -> bool { + unimplemented!() +} + +fn main() { + if bla1() { + println!("if"); + } + + if bla1() { + println!("if"); + } else { + println!("else"); + } + + if bla1() { + println!("if"); + } else if bla2() { + println!("else if"); + } else { + println!("else") + } + + if bla1() { + println!("if"); + } else if bla2() { + println!("else if 1"); + } else if bla3() { + println!("else if 2"); + } else { + println!("else") + } + + if bla1() { + println!("if"); + } else if bla2() { + //~ ERROR else if without else + println!("else if"); + } + + if bla1() { + println!("if"); + } else if bla2() { + println!("else if 1"); + } else if bla3() { + //~ ERROR else if without else + println!("else if 2"); + } +} diff --git a/src/tools/clippy/tests/ui/else_if_without_else.stderr b/src/tools/clippy/tests/ui/else_if_without_else.stderr new file mode 100644 index 0000000000000..6f47658cfb184 --- /dev/null +++ b/src/tools/clippy/tests/ui/else_if_without_else.stderr @@ -0,0 +1,27 @@ +error: `if` expression with an `else if`, but without a final `else` + --> $DIR/else_if_without_else.rs:45:12 + | +LL | } else if bla2() { + | ____________^ +LL | | //~ ERROR else if without else +LL | | println!("else if"); +LL | | } + | |_____^ + | + = note: `-D clippy::else-if-without-else` implied by `-D warnings` + = help: add an `else` block here + +error: `if` expression with an `else if`, but without a final `else` + --> $DIR/else_if_without_else.rs:54:12 + | +LL | } else if bla3() { + | ____________^ +LL | | //~ ERROR else if without else +LL | | println!("else if 2"); +LL | | } + | |_____^ + | + = help: add an `else` block here + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/empty_enum.rs b/src/tools/clippy/tests/ui/empty_enum.rs new file mode 100644 index 0000000000000..12428f29625c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_enum.rs @@ -0,0 +1,6 @@ +#![allow(dead_code)] +#![warn(clippy::empty_enum)] + +enum Empty {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_enum.stderr b/src/tools/clippy/tests/ui/empty_enum.stderr new file mode 100644 index 0000000000000..466dfbe7cee7a --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_enum.stderr @@ -0,0 +1,11 @@ +error: enum with no variants + --> $DIR/empty_enum.rs:4:1 + | +LL | enum Empty {} + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::empty-enum` implied by `-D warnings` + = help: consider using the uninhabited type `!` (never type) or a wrapper around it to introduce a type which can't be instantiated + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.rs b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.rs new file mode 100644 index 0000000000000..5343dff9da1db --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.rs @@ -0,0 +1,96 @@ +#![warn(clippy::empty_line_after_outer_attr)] +#![allow(clippy::assertions_on_constants)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +// This should produce a warning +#[crate_type = "lib"] + +/// some comment +fn with_one_newline_and_comment() { assert!(true) } + +// This should not produce a warning +#[crate_type = "lib"] +/// some comment +fn with_no_newline_and_comment() { assert!(true) } + + +// This should produce a warning +#[crate_type = "lib"] + +fn with_one_newline() { assert!(true) } + +// This should produce a warning, too +#[crate_type = "lib"] + + +fn with_two_newlines() { assert!(true) } + + +// This should produce a warning +#[crate_type = "lib"] + +enum Baz { + One, + Two +} + +// This should produce a warning +#[crate_type = "lib"] + +struct Foo { + one: isize, + two: isize +} + +// This should produce a warning +#[crate_type = "lib"] + +mod foo { +} + +/// This doc comment should not produce a warning + +/** This is also a doc comment and should not produce a warning + */ + +// This should not produce a warning +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[allow(missing_docs)] +fn three_attributes() { assert!(true) } + +// This should not produce a warning +#[doc = " +Returns the escaped value of the textual representation of + +"] +pub fn function() -> bool { + true +} + +// This should not produce a warning +#[derive(Clone, Copy)] +pub enum FooFighter { + Bar1, + + Bar2, + + Bar3, + + Bar4 +} + +// This should not produce a warning because the empty line is inside a block comment +#[crate_type = "lib"] +/* + +*/ +pub struct S; + +// This should not produce a warning +#[crate_type = "lib"] +/* test */ +pub struct T; + +fn main() { } diff --git a/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.stderr b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.stderr new file mode 100644 index 0000000000000..d8c9786541f0b --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_line_after_outer_attribute.stderr @@ -0,0 +1,54 @@ +error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:7:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | /// some comment +LL | | fn with_one_newline_and_comment() { assert!(true) } + | |_ + | + = note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings` + +error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:19:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | fn with_one_newline() { assert!(true) } + | |_ + +error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:24:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | +LL | | fn with_two_newlines() { assert!(true) } + | |_ + +error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:31:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | enum Baz { + | |_ + +error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:39:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | struct Foo { + | |_ + +error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? + --> $DIR/empty_line_after_outer_attribute.rs:47:1 + | +LL | / #[crate_type = "lib"] +LL | | +LL | | mod foo { + | |_ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/empty_loop.rs b/src/tools/clippy/tests/ui/empty_loop.rs new file mode 100644 index 0000000000000..8fd7697eb3b29 --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop.rs @@ -0,0 +1,51 @@ +// aux-build:macro_rules.rs + +#![warn(clippy::empty_loop)] + +#[macro_use] +extern crate macro_rules; + +fn should_trigger() { + loop {} + loop { + loop {} + } + + 'outer: loop { + 'inner: loop {} + } +} + +fn should_not_trigger() { + loop { + panic!("This is fine") + } + let ten_millis = std::time::Duration::from_millis(10); + loop { + std::thread::sleep(ten_millis) + } + + #[allow(clippy::never_loop)] + 'outer: loop { + 'inner: loop { + break 'inner; + } + break 'outer; + } + + // Make sure `allow` works for this lint + #[allow(clippy::empty_loop)] + loop {} + + // We don't lint loops inside macros + macro_rules! foo { + () => { + loop {} + }; + } + + // We don't lint external macros + foofoo!() +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/empty_loop.stderr b/src/tools/clippy/tests/ui/empty_loop.stderr new file mode 100644 index 0000000000000..e44c58ea770ae --- /dev/null +++ b/src/tools/clippy/tests/ui/empty_loop.stderr @@ -0,0 +1,22 @@ +error: empty `loop {}` detected. You may want to either use `panic!()` or add `std::thread::sleep(..);` to the loop body. + --> $DIR/empty_loop.rs:9:5 + | +LL | loop {} + | ^^^^^^^ + | + = note: `-D clippy::empty-loop` implied by `-D warnings` + +error: empty `loop {}` detected. You may want to either use `panic!()` or add `std::thread::sleep(..);` to the loop body. + --> $DIR/empty_loop.rs:11:9 + | +LL | loop {} + | ^^^^^^^ + +error: empty `loop {}` detected. You may want to either use `panic!()` or add `std::thread::sleep(..);` to the loop body. + --> $DIR/empty_loop.rs:15:9 + | +LL | 'inner: loop {} + | ^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/entry_fixable.fixed b/src/tools/clippy/tests/ui/entry_fixable.fixed new file mode 100644 index 0000000000000..dcdaae7e72430 --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_fixable.fixed @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(unused, clippy::needless_pass_by_value)] +#![warn(clippy::map_entry)] + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +fn foo() {} + +fn insert_if_absent0(m: &mut HashMap, k: K, v: V) { + m.entry(k).or_insert(v); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/entry_fixable.rs b/src/tools/clippy/tests/ui/entry_fixable.rs new file mode 100644 index 0000000000000..55d5b21568d0e --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_fixable.rs @@ -0,0 +1,17 @@ +// run-rustfix + +#![allow(unused, clippy::needless_pass_by_value)] +#![warn(clippy::map_entry)] + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +fn foo() {} + +fn insert_if_absent0(m: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + m.insert(k, v); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/entry_fixable.stderr b/src/tools/clippy/tests/ui/entry_fixable.stderr new file mode 100644 index 0000000000000..87403200ced50 --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_fixable.stderr @@ -0,0 +1,12 @@ +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_fixable.rs:12:5 + | +LL | / if !m.contains_key(&k) { +LL | | m.insert(k, v); +LL | | } + | |_____^ help: consider using: `m.entry(k).or_insert(v);` + | + = note: `-D clippy::map-entry` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/entry_unfixable.rs b/src/tools/clippy/tests/ui/entry_unfixable.rs new file mode 100644 index 0000000000000..f530fc023cfbf --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_unfixable.rs @@ -0,0 +1,73 @@ +#![allow(unused, clippy::needless_pass_by_value)] +#![warn(clippy::map_entry)] + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +fn foo() {} + +fn insert_if_absent2(m: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + m.insert(k, v) + } else { + None + }; +} + +fn insert_if_present2(m: &mut HashMap, k: K, v: V) { + if m.contains_key(&k) { + None + } else { + m.insert(k, v) + }; +} + +fn insert_if_absent3(m: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + foo(); + m.insert(k, v) + } else { + None + }; +} + +fn insert_if_present3(m: &mut HashMap, k: K, v: V) { + if m.contains_key(&k) { + None + } else { + foo(); + m.insert(k, v) + }; +} + +fn insert_in_btreemap(m: &mut BTreeMap, k: K, v: V) { + if !m.contains_key(&k) { + foo(); + m.insert(k, v) + } else { + None + }; +} + +// should not trigger +fn insert_other_if_absent(m: &mut HashMap, k: K, o: K, v: V) { + if !m.contains_key(&k) { + m.insert(o, v); + } +} + +// should not trigger, because the one uses different HashMap from another one +fn insert_from_different_map(m: HashMap, n: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + n.insert(k, v); + } +} + +// should not trigger, because the one uses different HashMap from another one +fn insert_from_different_map2(m: &mut HashMap, n: &mut HashMap, k: K, v: V) { + if !m.contains_key(&k) { + n.insert(k, v); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/entry_unfixable.stderr b/src/tools/clippy/tests/ui/entry_unfixable.stderr new file mode 100644 index 0000000000000..e58c8d22dc45e --- /dev/null +++ b/src/tools/clippy/tests/ui/entry_unfixable.stderr @@ -0,0 +1,57 @@ +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:10:5 + | +LL | / if !m.contains_key(&k) { +LL | | m.insert(k, v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ consider using `m.entry(k)` + | + = note: `-D clippy::map-entry` implied by `-D warnings` + +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:18:5 + | +LL | / if m.contains_key(&k) { +LL | | None +LL | | } else { +LL | | m.insert(k, v) +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:26:5 + | +LL | / if !m.contains_key(&k) { +LL | | foo(); +LL | | m.insert(k, v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: usage of `contains_key` followed by `insert` on a `HashMap` + --> $DIR/entry_unfixable.rs:35:5 + | +LL | / if m.contains_key(&k) { +LL | | None +LL | | } else { +LL | | foo(); +LL | | m.insert(k, v) +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: usage of `contains_key` followed by `insert` on a `BTreeMap` + --> $DIR/entry_unfixable.rs:44:5 + | +LL | / if !m.contains_key(&k) { +LL | | foo(); +LL | | m.insert(k, v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ consider using `m.entry(k)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_clike_unportable_variant.rs b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.rs new file mode 100644 index 0000000000000..7d6842f5b5421 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.rs @@ -0,0 +1,50 @@ +// ignore-x86 + +#![warn(clippy::enum_clike_unportable_variant)] +#![allow(unused, non_upper_case_globals)] + +#[repr(usize)] +enum NonPortable { + X = 0x1_0000_0000, + Y = 0, + Z = 0x7FFF_FFFF, + A = 0xFFFF_FFFF, +} + +enum NonPortableNoHint { + X = 0x1_0000_0000, + Y = 0, + Z = 0x7FFF_FFFF, + A = 0xFFFF_FFFF, +} + +#[repr(isize)] +enum NonPortableSigned { + X = -1, + Y = 0x7FFF_FFFF, + Z = 0xFFFF_FFFF, + A = 0x1_0000_0000, + B = i32::MIN as isize, + C = (i32::MIN as isize) - 1, +} + +enum NonPortableSignedNoHint { + X = -1, + Y = 0x7FFF_FFFF, + Z = 0xFFFF_FFFF, + A = 0x1_0000_0000, +} + +#[repr(usize)] +enum NonPortable2 { + X = ::Number, + Y = 0, +} + +trait Trait { + const Number: usize = 0x1_0000_0000; +} + +impl Trait for usize {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/enum_clike_unportable_variant.stderr b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.stderr new file mode 100644 index 0000000000000..71f3f5e083e0d --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_clike_unportable_variant.stderr @@ -0,0 +1,58 @@ +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:8:5 + | +LL | X = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::enum-clike-unportable-variant` implied by `-D warnings` + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:15:5 + | +LL | X = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:18:5 + | +LL | A = 0xFFFF_FFFF, + | ^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:25:5 + | +LL | Z = 0xFFFF_FFFF, + | ^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:26:5 + | +LL | A = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:28:5 + | +LL | C = (i32::MIN as isize) - 1, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:34:5 + | +LL | Z = 0xFFFF_FFFF, + | ^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:35:5 + | +LL | A = 0x1_0000_0000, + | ^^^^^^^^^^^^^^^^^ + +error: Clike enum variant discriminant is not portable to 32-bit targets + --> $DIR/enum_clike_unportable_variant.rs:40:5 + | +LL | X = ::Number, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_glob_use.fixed b/src/tools/clippy/tests/ui/enum_glob_use.fixed new file mode 100644 index 0000000000000..a98216758bb9e --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_glob_use.fixed @@ -0,0 +1,30 @@ +// run-rustfix + +#![warn(clippy::enum_glob_use)] +#![allow(unused)] +#![warn(unused_imports)] + +use std::cmp::Ordering::Less; + +enum Enum { + Foo, +} + +use self::Enum::Foo; + +mod in_fn_test { + fn blarg() { + use crate::Enum::Foo; + + let _ = Foo; + } +} + +mod blurg { + pub use std::cmp::Ordering::*; // ok, re-export +} + +fn main() { + let _ = Foo; + let _ = Less; +} diff --git a/src/tools/clippy/tests/ui/enum_glob_use.rs b/src/tools/clippy/tests/ui/enum_glob_use.rs new file mode 100644 index 0000000000000..5d929c9731d34 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_glob_use.rs @@ -0,0 +1,30 @@ +// run-rustfix + +#![warn(clippy::enum_glob_use)] +#![allow(unused)] +#![warn(unused_imports)] + +use std::cmp::Ordering::*; + +enum Enum { + Foo, +} + +use self::Enum::*; + +mod in_fn_test { + fn blarg() { + use crate::Enum::*; + + let _ = Foo; + } +} + +mod blurg { + pub use std::cmp::Ordering::*; // ok, re-export +} + +fn main() { + let _ = Foo; + let _ = Less; +} diff --git a/src/tools/clippy/tests/ui/enum_glob_use.stderr b/src/tools/clippy/tests/ui/enum_glob_use.stderr new file mode 100644 index 0000000000000..69531aed39bd4 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_glob_use.stderr @@ -0,0 +1,22 @@ +error: usage of wildcard import for enum variants + --> $DIR/enum_glob_use.rs:7:5 + | +LL | use std::cmp::Ordering::*; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `std::cmp::Ordering::Less` + | + = note: `-D clippy::enum-glob-use` implied by `-D warnings` + +error: usage of wildcard import for enum variants + --> $DIR/enum_glob_use.rs:13:5 + | +LL | use self::Enum::*; + | ^^^^^^^^^^^^^ help: try: `self::Enum::Foo` + +error: usage of wildcard import for enum variants + --> $DIR/enum_glob_use.rs:17:13 + | +LL | use crate::Enum::*; + | ^^^^^^^^^^^^^^ help: try: `crate::Enum::Foo` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_variants.rs b/src/tools/clippy/tests/ui/enum_variants.rs new file mode 100644 index 0000000000000..01774a2a9845c --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_variants.rs @@ -0,0 +1,136 @@ +#![feature(non_ascii_idents)] +#![warn(clippy::enum_variant_names, clippy::pub_enum_variant_names)] +#![allow(non_camel_case_types)] + +enum FakeCallType { + CALL, + CREATE, +} + +enum FakeCallType2 { + CALL, + CREATELL, +} + +enum Foo { + cFoo, + cBar, + cBaz, +} + +enum Fooo { + cFoo, // no error, threshold is 3 variants by default + cBar, +} + +enum Food { + FoodGood, + FoodMiddle, + FoodBad, +} + +enum Stuff { + StuffBad, // no error +} + +enum BadCallType { + CallTypeCall, + CallTypeCreate, + CallTypeDestroy, +} + +enum TwoCallType { + // no error + CallTypeCall, + CallTypeCreate, +} + +enum Consts { + ConstantInt, + ConstantCake, + ConstantLie, +} + +enum Two { + // no error here + ConstantInt, + ConstantInfer, +} + +enum Something { + CCall, + CCreate, + CCryogenize, +} + +enum Seal { + With, + Without, +} + +enum Seall { + With, + WithOut, + Withbroken, +} + +enum Sealll { + With, + WithOut, +} + +enum Seallll { + WithOutCake, + WithOutTea, + WithOut, +} + +enum NonCaps { + Prefix的, + PrefixTea, + PrefixCake, +} + +pub enum PubSeall { + WithOutCake, + WithOutTea, + WithOut, +} + +#[allow(clippy::pub_enum_variant_names)] +mod allowed { + pub enum PubAllowed { + SomeThis, + SomeThat, + SomeOtherWhat, + } +} + +// should not lint +enum Pat { + Foo, + Bar, + Path, +} + +// should not lint +enum N { + Pos, + Neg, + Float, +} + +// should not lint +enum Peek { + Peek1, + Peek2, + Peek3, +} + +// should not lint +pub enum NetworkLayer { + Layer2, + Layer3, +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/enum_variants.stderr b/src/tools/clippy/tests/ui/enum_variants.stderr new file mode 100644 index 0000000000000..2835391de7f58 --- /dev/null +++ b/src/tools/clippy/tests/ui/enum_variants.stderr @@ -0,0 +1,101 @@ +error: Variant name ends with the enum's name + --> $DIR/enum_variants.rs:16:5 + | +LL | cFoo, + | ^^^^ + | + = note: `-D clippy::enum-variant-names` implied by `-D warnings` + +error: Variant name starts with the enum's name + --> $DIR/enum_variants.rs:27:5 + | +LL | FoodGood, + | ^^^^^^^^ + +error: Variant name starts with the enum's name + --> $DIR/enum_variants.rs:28:5 + | +LL | FoodMiddle, + | ^^^^^^^^^^ + +error: Variant name starts with the enum's name + --> $DIR/enum_variants.rs:29:5 + | +LL | FoodBad, + | ^^^^^^^ + +error: All variants have the same prefix: `Food` + --> $DIR/enum_variants.rs:26:1 + | +LL | / enum Food { +LL | | FoodGood, +LL | | FoodMiddle, +LL | | FoodBad, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: All variants have the same prefix: `CallType` + --> $DIR/enum_variants.rs:36:1 + | +LL | / enum BadCallType { +LL | | CallTypeCall, +LL | | CallTypeCreate, +LL | | CallTypeDestroy, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: All variants have the same prefix: `Constant` + --> $DIR/enum_variants.rs:48:1 + | +LL | / enum Consts { +LL | | ConstantInt, +LL | | ConstantCake, +LL | | ConstantLie, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: All variants have the same prefix: `With` + --> $DIR/enum_variants.rs:82:1 + | +LL | / enum Seallll { +LL | | WithOutCake, +LL | | WithOutTea, +LL | | WithOut, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: All variants have the same prefix: `Prefix` + --> $DIR/enum_variants.rs:88:1 + | +LL | / enum NonCaps { +LL | | Prefix的, +LL | | PrefixTea, +LL | | PrefixCake, +LL | | } + | |_^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: All variants have the same prefix: `With` + --> $DIR/enum_variants.rs:94:1 + | +LL | / pub enum PubSeall { +LL | | WithOutCake, +LL | | WithOutTea, +LL | | WithOut, +LL | | } + | |_^ + | + = note: `-D clippy::pub-enum-variant-names` implied by `-D warnings` + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/eq_op.rs b/src/tools/clippy/tests/ui/eq_op.rs new file mode 100644 index 0000000000000..272b0900a31c6 --- /dev/null +++ b/src/tools/clippy/tests/ui/eq_op.rs @@ -0,0 +1,87 @@ +// does not test any rustfixable lints + +#[rustfmt::skip] +#[warn(clippy::eq_op)] +#[allow(clippy::identity_op, clippy::double_parens, clippy::many_single_char_names)] +#[allow(clippy::no_effect, unused_variables, clippy::unnecessary_operation, clippy::short_circuit_statement)] +#[allow(clippy::nonminimal_bool)] +#[allow(unused)] +fn main() { + // simple values and comparisons + 1 == 1; + "no" == "no"; + // even though I agree that no means no ;-) + false != false; + 1.5 < 1.5; + 1u64 >= 1u64; + + // casts, methods, parentheses + (1 as u64) & (1 as u64); + 1 ^ ((((((1)))))); + + // unary and binary operators + (-(2) < -(2)); + ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; + + // various other things + ([1] != [1]); + ((1, 2) != (1, 2)); + vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros + + // const folding + 1 + 1 == 2; + 1 - 1 == 0; + + 1 - 1; + 1 / 1; + true && true; + + true || true; + + + let a: u32 = 0; + let b: u32 = 0; + + a == b && b == a; + a != b && b != a; + a < b && b > a; + a <= b && b >= a; + + let mut a = vec![1]; + a == a; + 2*a.len() == 2*a.len(); // ok, functions + a.pop() == a.pop(); // ok, functions + + check_ignore_macro(); + + // named constants + const A: u32 = 10; + const B: u32 = 10; + const C: u32 = A / B; // ok, different named constants + const D: u32 = A / A; +} + +#[rustfmt::skip] +macro_rules! check_if_named_foo { + ($expression:expr) => ( + if stringify!($expression) == "foo" { + println!("foo!"); + } else { + println!("not foo."); + } + ) +} + +macro_rules! bool_macro { + ($expression:expr) => { + true + }; +} + +#[allow(clippy::short_circuit_statement)] +fn check_ignore_macro() { + check_if_named_foo!(foo); + // checks if the lint ignores macros with `!` operator + !bool_macro!(1) && !bool_macro!(""); +} diff --git a/src/tools/clippy/tests/ui/eq_op.stderr b/src/tools/clippy/tests/ui/eq_op.stderr new file mode 100644 index 0000000000000..5b80e6078eed7 --- /dev/null +++ b/src/tools/clippy/tests/ui/eq_op.stderr @@ -0,0 +1,166 @@ +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:11:5 + | +LL | 1 == 1; + | ^^^^^^ + | + = note: `-D clippy::eq-op` implied by `-D warnings` + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:12:5 + | +LL | "no" == "no"; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/eq_op.rs:14:5 + | +LL | false != false; + | ^^^^^^^^^^^^^^ + +error: equal expressions as operands to `<` + --> $DIR/eq_op.rs:15:5 + | +LL | 1.5 < 1.5; + | ^^^^^^^^^ + +error: equal expressions as operands to `>=` + --> $DIR/eq_op.rs:16:5 + | +LL | 1u64 >= 1u64; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `&` + --> $DIR/eq_op.rs:19:5 + | +LL | (1 as u64) & (1 as u64); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `^` + --> $DIR/eq_op.rs:20:5 + | +LL | 1 ^ ((((((1)))))); + | ^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `<` + --> $DIR/eq_op.rs:23:5 + | +LL | (-(2) < -(2)); + | ^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:24:5 + | +LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&` + --> $DIR/eq_op.rs:24:6 + | +LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&` + --> $DIR/eq_op.rs:24:27 + | +LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:25:5 + | +LL | (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/eq_op.rs:28:5 + | +LL | ([1] != [1]); + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `!=` + --> $DIR/eq_op.rs:29:5 + | +LL | ((1, 2) != (1, 2)); + | ^^^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:33:5 + | +LL | 1 + 1 == 2; + | ^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:34:5 + | +LL | 1 - 1 == 0; + | ^^^^^^^^^^ + +error: equal expressions as operands to `-` + --> $DIR/eq_op.rs:34:5 + | +LL | 1 - 1 == 0; + | ^^^^^ + +error: equal expressions as operands to `-` + --> $DIR/eq_op.rs:36:5 + | +LL | 1 - 1; + | ^^^^^ + +error: equal expressions as operands to `/` + --> $DIR/eq_op.rs:37:5 + | +LL | 1 / 1; + | ^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:38:5 + | +LL | true && true; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `||` + --> $DIR/eq_op.rs:40:5 + | +LL | true || true; + | ^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:46:5 + | +LL | a == b && b == a; + | ^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:47:5 + | +LL | a != b && b != a; + | ^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:48:5 + | +LL | a < b && b > a; + | ^^^^^^^^^^^^^^ + +error: equal expressions as operands to `&&` + --> $DIR/eq_op.rs:49:5 + | +LL | a <= b && b >= a; + | ^^^^^^^^^^^^^^^^ + +error: equal expressions as operands to `==` + --> $DIR/eq_op.rs:52:5 + | +LL | a == a; + | ^^^^^^ + +error: equal expressions as operands to `/` + --> $DIR/eq_op.rs:62:20 + | +LL | const D: u32 = A / A; + | ^^^^^ + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/erasing_op.rs b/src/tools/clippy/tests/ui/erasing_op.rs new file mode 100644 index 0000000000000..1540062a4bc3e --- /dev/null +++ b/src/tools/clippy/tests/ui/erasing_op.rs @@ -0,0 +1,9 @@ +#[allow(clippy::no_effect)] +#[warn(clippy::erasing_op)] +fn main() { + let x: u8 = 0; + + x * 0; + 0 & x; + 0 / x; +} diff --git a/src/tools/clippy/tests/ui/erasing_op.stderr b/src/tools/clippy/tests/ui/erasing_op.stderr new file mode 100644 index 0000000000000..e54ce85f98ec7 --- /dev/null +++ b/src/tools/clippy/tests/ui/erasing_op.stderr @@ -0,0 +1,22 @@ +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/erasing_op.rs:6:5 + | +LL | x * 0; + | ^^^^^ + | + = note: `-D clippy::erasing-op` implied by `-D warnings` + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/erasing_op.rs:7:5 + | +LL | 0 & x; + | ^^^^^ + +error: this operation will always return zero. This is likely not the intended outcome + --> $DIR/erasing_op.rs:8:5 + | +LL | 0 / x; + | ^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/escape_analysis.rs b/src/tools/clippy/tests/ui/escape_analysis.rs new file mode 100644 index 0000000000000..c0a52d832c00a --- /dev/null +++ b/src/tools/clippy/tests/ui/escape_analysis.rs @@ -0,0 +1,176 @@ +#![feature(box_syntax)] +#![allow( + clippy::borrowed_box, + clippy::needless_pass_by_value, + clippy::unused_unit, + clippy::redundant_clone, + clippy::match_single_binding +)] +#![warn(clippy::boxed_local)] + +#[derive(Clone)] +struct A; + +impl A { + fn foo(&self) {} +} + +trait Z { + fn bar(&self); +} + +impl Z for A { + fn bar(&self) { + //nothing + } +} + +fn main() {} + +fn ok_box_trait(boxed_trait: &Box) { + let boxed_local = boxed_trait; + // done +} + +fn warn_call() { + let x = box A; + x.foo(); +} + +fn warn_arg(x: Box) { + x.foo(); +} + +fn nowarn_closure_arg() { + let x = Some(box A); + x.map_or((), |x| take_ref(&x)); +} + +fn warn_rename_call() { + let x = box A; + + let y = x; + y.foo(); // via autoderef +} + +fn warn_notuse() { + let bz = box A; +} + +fn warn_pass() { + let bz = box A; + take_ref(&bz); // via deref coercion +} + +fn nowarn_return() -> Box { + box A // moved out, "escapes" +} + +fn nowarn_move() { + let bx = box A; + drop(bx) // moved in, "escapes" +} +fn nowarn_call() { + let bx = box A; + bx.clone(); // method only available to Box, not via autoderef +} + +fn nowarn_pass() { + let bx = box A; + take_box(&bx); // fn needs &Box +} + +fn take_box(x: &Box) {} +fn take_ref(x: &A) {} + +fn nowarn_ref_take() { + // false positive, should actually warn + let x = box A; + let y = &x; + take_box(y); +} + +fn nowarn_match() { + let x = box A; // moved into a match + match x { + y => drop(y), + } +} + +fn warn_match() { + let x = box A; + match &x { + // not moved + ref y => (), + } +} + +fn nowarn_large_array() { + // should not warn, is large array + // and should not be on stack + let x = box [1; 10000]; + match &x { + // not moved + ref y => (), + } +} + +/// ICE regression test +pub trait Foo { + type Item; +} + +impl<'a> Foo for &'a () { + type Item = (); +} + +pub struct PeekableSeekable { + _peeked: I::Item, +} + +pub fn new(_needs_name: Box>) -> () {} + +/// Regression for #916, #1123 +/// +/// This shouldn't warn for `boxed_local`as the implementation of a trait +/// can't change much about the trait definition. +trait BoxedAction { + fn do_sth(self: Box); +} + +impl BoxedAction for u64 { + fn do_sth(self: Box) { + println!("{}", *self) + } +} + +/// Regression for #1478 +/// +/// This shouldn't warn for `boxed_local`as self itself is a box type. +trait MyTrait { + fn do_sth(self); +} + +impl MyTrait for Box { + fn do_sth(self) {} +} + +// Issue #3739 - capture in closures +mod issue_3739 { + use super::A; + + fn consume(_: T) {} + fn borrow(_: &T) {} + + fn closure_consume(x: Box) { + let _ = move || { + consume(x); + }; + } + + fn closure_borrow(x: Box) { + let _ = || { + borrow(&x); + }; + } +} diff --git a/src/tools/clippy/tests/ui/escape_analysis.stderr b/src/tools/clippy/tests/ui/escape_analysis.stderr new file mode 100644 index 0000000000000..c86a769a3da4b --- /dev/null +++ b/src/tools/clippy/tests/ui/escape_analysis.stderr @@ -0,0 +1,16 @@ +error: local variable doesn't need to be boxed here + --> $DIR/escape_analysis.rs:40:13 + | +LL | fn warn_arg(x: Box) { + | ^ + | + = note: `-D clippy::boxed-local` implied by `-D warnings` + +error: local variable doesn't need to be boxed here + --> $DIR/escape_analysis.rs:131:12 + | +LL | pub fn new(_needs_name: Box>) -> () {} + | ^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/eta.fixed b/src/tools/clippy/tests/ui/eta.fixed new file mode 100644 index 0000000000000..1b34c2f74eba1 --- /dev/null +++ b/src/tools/clippy/tests/ui/eta.fixed @@ -0,0 +1,204 @@ +// run-rustfix + +#![allow( + unused, + clippy::no_effect, + clippy::redundant_closure_call, + clippy::many_single_char_names, + clippy::needless_pass_by_value, + clippy::option_map_unit_fn +)] +#![warn( + clippy::redundant_closure, + clippy::redundant_closure_for_method_calls, + clippy::needless_borrow +)] + +use std::path::PathBuf; + +fn main() { + let a = Some(1u8).map(foo); + meta(foo); + let c = Some(1u8).map(|a| {1+2; foo}(a)); + let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted? + all(&[1, 2, 3], &2, |x, y| below(x, y)); //is adjusted + unsafe { + Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn + } + + // See #815 + let e = Some(1u8).map(|a| divergent(a)); + let e = Some(1u8).map(generic); + let e = Some(1u8).map(generic); + // See #515 + let a: Option)>> = + Some(vec![1i32, 2]).map(|v| -> Box)> { Box::new(v) }); +} + +trait TestTrait { + fn trait_foo(self) -> bool; + fn trait_foo_ref(&self) -> bool; +} + +struct TestStruct<'a> { + some_ref: &'a i32, +} + +impl<'a> TestStruct<'a> { + fn foo(self) -> bool { + false + } + unsafe fn foo_unsafe(self) -> bool { + true + } +} + +impl<'a> TestTrait for TestStruct<'a> { + fn trait_foo(self) -> bool { + false + } + fn trait_foo_ref(&self) -> bool { + false + } +} + +impl<'a> std::ops::Deref for TestStruct<'a> { + type Target = char; + fn deref(&self) -> &char { + &'a' + } +} + +fn test_redundant_closures_containing_method_calls() { + let i = 10; + let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo); + let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo); + let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo); + let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo_ref()); + let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo); + let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear); + let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear); + unsafe { + let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo_unsafe()); + } + let e = Some("str").map(std::string::ToString::to_string); + let e = Some("str").map(str::to_string); + let e = Some('a').map(char::to_uppercase); + let e = Some('a').map(char::to_uppercase); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.len_utf8()).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect(); + let p = Some(PathBuf::new()); + let e = p.as_ref().and_then(|s| s.to_str()); + let c = Some(TestStruct { some_ref: &i }) + .as_ref() + .map(|c| c.to_ascii_uppercase()); + + fn test_different_borrow_levels(t: &[&T]) + where + T: TestTrait, + { + t.iter().filter(|x| x.trait_foo_ref()); + t.iter().map(|x| x.trait_foo_ref()); + } + + let mut some = Some(|x| x * x); + let arr = [Ok(1), Err(2)]; + let _: Vec<_> = arr.iter().map(|x| x.map_err(|e| some.take().unwrap()(e))).collect(); +} + +struct Thunk(Box T>); + +impl Thunk { + fn new T>(f: F) -> Thunk { + let mut option = Some(f); + // This should not trigger redundant_closure (#1439) + Thunk(Box::new(move || option.take().unwrap()())) + } + + fn unwrap(self) -> T { + let Thunk(mut f) = self; + f() + } +} + +fn foobar() { + let thunk = Thunk::new(|| println!("Hello, world!")); + thunk.unwrap() +} + +fn meta(f: F) +where + F: Fn(u8), +{ + f(1u8) +} + +fn foo(_: u8) {} + +fn foo2(_: u8) -> u8 { + 1u8 +} + +fn all(x: &[X], y: &X, f: F) -> bool +where + F: Fn(&X, &X) -> bool, +{ + x.iter().all(|e| f(e, y)) +} + +fn below(x: &u8, y: &u8) -> bool { + x < y +} + +unsafe fn unsafe_fn(_: u8) {} + +fn divergent(_: u8) -> ! { + unimplemented!() +} + +fn generic(_: T) -> u8 { + 0 +} + +fn passes_fn_mut(mut x: Box) { + requires_fn_once(|| x()); +} +fn requires_fn_once(_: T) {} + +fn test_redundant_closure_with_function_pointer() { + type FnPtrType = fn(u8); + let foo_ptr: FnPtrType = foo; + let a = Some(1u8).map(foo_ptr); +} + +fn test_redundant_closure_with_another_closure() { + let closure = |a| println!("{}", a); + let a = Some(1u8).map(closure); +} + +fn make_lazy(f: impl Fn() -> fn(u8) -> u8) -> impl Fn(u8) -> u8 { + // Currently f is called when result of make_lazy is called. + // If the closure is removed, f will be called when make_lazy itself is + // called. This changes semantics, so the closure must stay. + Box::new(move |x| f()(x)) +} + +fn call String>(f: F) -> String { + f(&mut "Hello".to_owned()) +} +fn test_difference_in_mutability() { + call(|s| s.clone()); +} + +struct Bar; +impl std::ops::Deref for Bar { + type Target = str; + fn deref(&self) -> &str { + "hi" + } +} + +fn test_deref_with_trait_method() { + let _ = [Bar].iter().map(|s| s.to_string()).collect::>(); +} diff --git a/src/tools/clippy/tests/ui/eta.rs b/src/tools/clippy/tests/ui/eta.rs new file mode 100644 index 0000000000000..4f050bd8479ae --- /dev/null +++ b/src/tools/clippy/tests/ui/eta.rs @@ -0,0 +1,204 @@ +// run-rustfix + +#![allow( + unused, + clippy::no_effect, + clippy::redundant_closure_call, + clippy::many_single_char_names, + clippy::needless_pass_by_value, + clippy::option_map_unit_fn +)] +#![warn( + clippy::redundant_closure, + clippy::redundant_closure_for_method_calls, + clippy::needless_borrow +)] + +use std::path::PathBuf; + +fn main() { + let a = Some(1u8).map(|a| foo(a)); + meta(|a| foo(a)); + let c = Some(1u8).map(|a| {1+2; foo}(a)); + let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted? + all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted + unsafe { + Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn + } + + // See #815 + let e = Some(1u8).map(|a| divergent(a)); + let e = Some(1u8).map(|a| generic(a)); + let e = Some(1u8).map(generic); + // See #515 + let a: Option)>> = + Some(vec![1i32, 2]).map(|v| -> Box)> { Box::new(v) }); +} + +trait TestTrait { + fn trait_foo(self) -> bool; + fn trait_foo_ref(&self) -> bool; +} + +struct TestStruct<'a> { + some_ref: &'a i32, +} + +impl<'a> TestStruct<'a> { + fn foo(self) -> bool { + false + } + unsafe fn foo_unsafe(self) -> bool { + true + } +} + +impl<'a> TestTrait for TestStruct<'a> { + fn trait_foo(self) -> bool { + false + } + fn trait_foo_ref(&self) -> bool { + false + } +} + +impl<'a> std::ops::Deref for TestStruct<'a> { + type Target = char; + fn deref(&self) -> &char { + &'a' + } +} + +fn test_redundant_closures_containing_method_calls() { + let i = 10; + let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); + let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo); + let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo()); + let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo_ref()); + let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo); + let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear()); + let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear); + unsafe { + let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo_unsafe()); + } + let e = Some("str").map(|s| s.to_string()); + let e = Some("str").map(str::to_string); + let e = Some('a').map(|s| s.to_uppercase()); + let e = Some('a').map(char::to_uppercase); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.len_utf8()).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect(); + let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect(); + let p = Some(PathBuf::new()); + let e = p.as_ref().and_then(|s| s.to_str()); + let c = Some(TestStruct { some_ref: &i }) + .as_ref() + .map(|c| c.to_ascii_uppercase()); + + fn test_different_borrow_levels(t: &[&T]) + where + T: TestTrait, + { + t.iter().filter(|x| x.trait_foo_ref()); + t.iter().map(|x| x.trait_foo_ref()); + } + + let mut some = Some(|x| x * x); + let arr = [Ok(1), Err(2)]; + let _: Vec<_> = arr.iter().map(|x| x.map_err(|e| some.take().unwrap()(e))).collect(); +} + +struct Thunk(Box T>); + +impl Thunk { + fn new T>(f: F) -> Thunk { + let mut option = Some(f); + // This should not trigger redundant_closure (#1439) + Thunk(Box::new(move || option.take().unwrap()())) + } + + fn unwrap(self) -> T { + let Thunk(mut f) = self; + f() + } +} + +fn foobar() { + let thunk = Thunk::new(|| println!("Hello, world!")); + thunk.unwrap() +} + +fn meta(f: F) +where + F: Fn(u8), +{ + f(1u8) +} + +fn foo(_: u8) {} + +fn foo2(_: u8) -> u8 { + 1u8 +} + +fn all(x: &[X], y: &X, f: F) -> bool +where + F: Fn(&X, &X) -> bool, +{ + x.iter().all(|e| f(e, y)) +} + +fn below(x: &u8, y: &u8) -> bool { + x < y +} + +unsafe fn unsafe_fn(_: u8) {} + +fn divergent(_: u8) -> ! { + unimplemented!() +} + +fn generic(_: T) -> u8 { + 0 +} + +fn passes_fn_mut(mut x: Box) { + requires_fn_once(|| x()); +} +fn requires_fn_once(_: T) {} + +fn test_redundant_closure_with_function_pointer() { + type FnPtrType = fn(u8); + let foo_ptr: FnPtrType = foo; + let a = Some(1u8).map(|a| foo_ptr(a)); +} + +fn test_redundant_closure_with_another_closure() { + let closure = |a| println!("{}", a); + let a = Some(1u8).map(|a| closure(a)); +} + +fn make_lazy(f: impl Fn() -> fn(u8) -> u8) -> impl Fn(u8) -> u8 { + // Currently f is called when result of make_lazy is called. + // If the closure is removed, f will be called when make_lazy itself is + // called. This changes semantics, so the closure must stay. + Box::new(move |x| f()(x)) +} + +fn call String>(f: F) -> String { + f(&mut "Hello".to_owned()) +} +fn test_difference_in_mutability() { + call(|s| s.clone()); +} + +struct Bar; +impl std::ops::Deref for Bar { + type Target = str; + fn deref(&self) -> &str { + "hi" + } +} + +fn test_deref_with_trait_method() { + let _ = [Bar].iter().map(|s| s.to_string()).collect::>(); +} diff --git a/src/tools/clippy/tests/ui/eta.stderr b/src/tools/clippy/tests/ui/eta.stderr new file mode 100644 index 0000000000000..c4713ca8083dd --- /dev/null +++ b/src/tools/clippy/tests/ui/eta.stderr @@ -0,0 +1,80 @@ +error: redundant closure found + --> $DIR/eta.rs:20:27 + | +LL | let a = Some(1u8).map(|a| foo(a)); + | ^^^^^^^^^^ help: remove closure as shown: `foo` + | + = note: `-D clippy::redundant-closure` implied by `-D warnings` + +error: redundant closure found + --> $DIR/eta.rs:21:10 + | +LL | meta(|a| foo(a)); + | ^^^^^^^^^^ help: remove closure as shown: `foo` + +error: this expression borrows a reference that is immediately dereferenced by the compiler + --> $DIR/eta.rs:24:21 + | +LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted + | ^^^ help: change this to: `&2` + | + = note: `-D clippy::needless-borrow` implied by `-D warnings` + +error: redundant closure found + --> $DIR/eta.rs:31:27 + | +LL | let e = Some(1u8).map(|a| generic(a)); + | ^^^^^^^^^^^^^^ help: remove closure as shown: `generic` + +error: redundant closure found + --> $DIR/eta.rs:74:51 + | +LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); + | ^^^^^^^^^^^ help: remove closure as shown: `TestStruct::foo` + | + = note: `-D clippy::redundant-closure-for-method-calls` implied by `-D warnings` + +error: redundant closure found + --> $DIR/eta.rs:76:51 + | +LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo()); + | ^^^^^^^^^^^^^^^^^ help: remove closure as shown: `TestTrait::trait_foo` + +error: redundant closure found + --> $DIR/eta.rs:79:42 + | +LL | let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear()); + | ^^^^^^^^^^^^^ help: remove closure as shown: `std::vec::Vec::clear` + +error: redundant closure found + --> $DIR/eta.rs:84:29 + | +LL | let e = Some("str").map(|s| s.to_string()); + | ^^^^^^^^^^^^^^^^^ help: remove closure as shown: `std::string::ToString::to_string` + +error: redundant closure found + --> $DIR/eta.rs:86:27 + | +LL | let e = Some('a').map(|s| s.to_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^ help: remove closure as shown: `char::to_uppercase` + +error: redundant closure found + --> $DIR/eta.rs:89:65 + | +LL | let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove closure as shown: `char::to_ascii_uppercase` + +error: redundant closure found + --> $DIR/eta.rs:172:27 + | +LL | let a = Some(1u8).map(|a| foo_ptr(a)); + | ^^^^^^^^^^^^^^ help: remove closure as shown: `foo_ptr` + +error: redundant closure found + --> $DIR/eta.rs:177:27 + | +LL | let a = Some(1u8).map(|a| closure(a)); + | ^^^^^^^^^^^^^^ help: remove closure as shown: `closure` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/eval_order_dependence.rs b/src/tools/clippy/tests/ui/eval_order_dependence.rs new file mode 100644 index 0000000000000..d806bc6d40102 --- /dev/null +++ b/src/tools/clippy/tests/ui/eval_order_dependence.rs @@ -0,0 +1,109 @@ +#[warn(clippy::eval_order_dependence)] +#[allow( + unused_assignments, + unused_variables, + clippy::many_single_char_names, + clippy::no_effect, + dead_code, + clippy::blacklisted_name +)] +fn main() { + let mut x = 0; + let a = { + x = 1; + 1 + } + x; + + // Example from iss#277 + x += { + x = 20; + 2 + }; + + // Does it work in weird places? + // ...in the base for a struct expression? + struct Foo { + a: i32, + b: i32, + }; + let base = Foo { a: 4, b: 5 }; + let foo = Foo { + a: x, + ..{ + x = 6; + base + } + }; + // ...inside a closure? + let closure = || { + let mut x = 0; + x += { + x = 20; + 2 + }; + }; + // ...not across a closure? + let mut y = 0; + let b = (y, || y = 1); + + // && and || evaluate left-to-right. + let a = { + x = 1; + true + } && (x == 3); + let a = { + x = 1; + true + } || (x == 3); + + // Make sure we don't get confused by alpha conversion. + let a = { + let mut x = 1; + x = 2; + 1 + } + x; + + // No warning if we don't read the variable... + x = { + x = 20; + 2 + }; + // ...if the assignment is in a closure... + let b = { + || { + x = 1; + }; + 1 + } + x; + // ... or the access is under an address. + let b = ( + { + let p = &x; + 1 + }, + { + x = 1; + x + }, + ); + + // Limitation: l-values other than simple variables don't trigger + // the warning. + let mut tup = (0, 0); + let c = { + tup.0 = 1; + 1 + } + tup.0; + // Limitation: you can get away with a read under address-of. + let mut z = 0; + let b = ( + &{ + z = x; + x + }, + { + x = 3; + x + }, + ); +} diff --git a/src/tools/clippy/tests/ui/eval_order_dependence.stderr b/src/tools/clippy/tests/ui/eval_order_dependence.stderr new file mode 100644 index 0000000000000..8f4fa2228f7f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/eval_order_dependence.stderr @@ -0,0 +1,51 @@ +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:15:9 + | +LL | } + x; + | ^ + | + = note: `-D clippy::eval-order-dependence` implied by `-D warnings` +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:13:9 + | +LL | x = 1; + | ^^^^^ + +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:18:5 + | +LL | x += { + | ^ + | +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:19:9 + | +LL | x = 20; + | ^^^^^^ + +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:31:12 + | +LL | a: x, + | ^ + | +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:33:13 + | +LL | x = 6; + | ^^^^^ + +error: unsequenced read of a variable + --> $DIR/eval_order_dependence.rs:40:9 + | +LL | x += { + | ^ + | +note: whether read occurs before this write depends on evaluation order + --> $DIR/eval_order_dependence.rs:41:13 + | +LL | x = 20; + | ^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/excessive_precision.fixed b/src/tools/clippy/tests/ui/excessive_precision.fixed new file mode 100644 index 0000000000000..bf0325fec7923 --- /dev/null +++ b/src/tools/clippy/tests/ui/excessive_precision.fixed @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::excessive_precision)] +#![allow(dead_code, unused_variables, clippy::print_literal)] + +fn main() { + // Consts + const GOOD32: f32 = 0.123_456; + const GOOD32_SM: f32 = 0.000_000_000_1; + const GOOD32_DOT: f32 = 10_000_000_000.0; + const GOOD32_EDGE: f32 = 1.000_000_8; + const GOOD64: f64 = 0.123_456_789_012; + const GOOD64_SM: f32 = 0.000_000_000_000_000_1; + const GOOD64_DOT: f32 = 10_000_000_000_000_000.0; + + const BAD32_1: f32 = 0.123_456_79_f32; + const BAD32_2: f32 = 0.123_456_79; + const BAD32_3: f32 = 0.1; + const BAD32_EDGE: f32 = 1.000_001; + + const BAD64_1: f64 = 0.123_456_789_012_345_66_f64; + const BAD64_2: f64 = 0.123_456_789_012_345_66; + const BAD64_3: f64 = 0.1; + + // Literal as param + println!("{:?}", 8.888_888_888_888_89); + + // // TODO add inferred type tests for f32 + // Locals + let good32: f32 = 0.123_456_f32; + let good32_2: f32 = 0.123_456; + + let good64: f64 = 0.123_456_789_012; + let good64_suf: f64 = 0.123_456_789_012f64; + let good64_inf = 0.123_456_789_012; + + let bad32: f32 = 1.123_456_8; + let bad32_suf: f32 = 1.123_456_8_f32; + let bad32_inf = 1.123_456_8_f32; + + let bad64: f64 = 0.123_456_789_012_345_66; + let bad64_suf: f64 = 0.123_456_789_012_345_66_f64; + let bad64_inf = 0.123_456_789_012_345_66; + + // Vectors + let good_vec32: Vec = vec![0.123_456]; + let good_vec64: Vec = vec![0.123_456_789]; + + let bad_vec32: Vec = vec![0.123_456_79]; + let bad_vec64: Vec = vec![0.123_456_789_123_456_78]; + + // Exponential float notation + let good_e32: f32 = 1e-10; + let bad_e32: f32 = 1.123_456_8e-10; + + let good_bige32: f32 = 1E-10; + let bad_bige32: f32 = 1.123_456_8E-10; + + // Inferred type + let good_inferred: f32 = 1f32 * 1_000_000_000.; + + // issue #2840 + let num = 0.000_000_000_01e-10f64; +} diff --git a/src/tools/clippy/tests/ui/excessive_precision.rs b/src/tools/clippy/tests/ui/excessive_precision.rs new file mode 100644 index 0000000000000..ce4722a90f900 --- /dev/null +++ b/src/tools/clippy/tests/ui/excessive_precision.rs @@ -0,0 +1,63 @@ +// run-rustfix +#![warn(clippy::excessive_precision)] +#![allow(dead_code, unused_variables, clippy::print_literal)] + +fn main() { + // Consts + const GOOD32: f32 = 0.123_456; + const GOOD32_SM: f32 = 0.000_000_000_1; + const GOOD32_DOT: f32 = 10_000_000_000.0; + const GOOD32_EDGE: f32 = 1.000_000_8; + const GOOD64: f64 = 0.123_456_789_012; + const GOOD64_SM: f32 = 0.000_000_000_000_000_1; + const GOOD64_DOT: f32 = 10_000_000_000_000_000.0; + + const BAD32_1: f32 = 0.123_456_789_f32; + const BAD32_2: f32 = 0.123_456_789; + const BAD32_3: f32 = 0.100_000_000_000_1; + const BAD32_EDGE: f32 = 1.000_000_9; + + const BAD64_1: f64 = 0.123_456_789_012_345_67f64; + const BAD64_2: f64 = 0.123_456_789_012_345_67; + const BAD64_3: f64 = 0.100_000_000_000_000_000_1; + + // Literal as param + println!("{:?}", 8.888_888_888_888_888_888_888); + + // // TODO add inferred type tests for f32 + // Locals + let good32: f32 = 0.123_456_f32; + let good32_2: f32 = 0.123_456; + + let good64: f64 = 0.123_456_789_012; + let good64_suf: f64 = 0.123_456_789_012f64; + let good64_inf = 0.123_456_789_012; + + let bad32: f32 = 1.123_456_789; + let bad32_suf: f32 = 1.123_456_789_f32; + let bad32_inf = 1.123_456_789_f32; + + let bad64: f64 = 0.123_456_789_012_345_67; + let bad64_suf: f64 = 0.123_456_789_012_345_67f64; + let bad64_inf = 0.123_456_789_012_345_67; + + // Vectors + let good_vec32: Vec = vec![0.123_456]; + let good_vec64: Vec = vec![0.123_456_789]; + + let bad_vec32: Vec = vec![0.123_456_789]; + let bad_vec64: Vec = vec![0.123_456_789_123_456_789]; + + // Exponential float notation + let good_e32: f32 = 1e-10; + let bad_e32: f32 = 1.123_456_788_888e-10; + + let good_bige32: f32 = 1E-10; + let bad_bige32: f32 = 1.123_456_788_888E-10; + + // Inferred type + let good_inferred: f32 = 1f32 * 1_000_000_000.; + + // issue #2840 + let num = 0.000_000_000_01e-10f64; +} diff --git a/src/tools/clippy/tests/ui/excessive_precision.stderr b/src/tools/clippy/tests/ui/excessive_precision.stderr new file mode 100644 index 0000000000000..599773f2f70c4 --- /dev/null +++ b/src/tools/clippy/tests/ui/excessive_precision.stderr @@ -0,0 +1,112 @@ +error: float has excessive precision + --> $DIR/excessive_precision.rs:15:26 + | +LL | const BAD32_1: f32 = 0.123_456_789_f32; + | ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_79_f32` + | + = note: `-D clippy::excessive-precision` implied by `-D warnings` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:16:26 + | +LL | const BAD32_2: f32 = 0.123_456_789; + | ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_79` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:17:26 + | +LL | const BAD32_3: f32 = 0.100_000_000_000_1; + | ^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:18:29 + | +LL | const BAD32_EDGE: f32 = 1.000_000_9; + | ^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.000_001` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:20:26 + | +LL | const BAD64_1: f64 = 0.123_456_789_012_345_67f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66_f64` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:21:26 + | +LL | const BAD64_2: f64 = 0.123_456_789_012_345_67; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:22:26 + | +LL | const BAD64_3: f64 = 0.100_000_000_000_000_000_1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.1` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:25:22 + | +LL | println!("{:?}", 8.888_888_888_888_888_888_888); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `8.888_888_888_888_89` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:36:22 + | +LL | let bad32: f32 = 1.123_456_789; + | ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:37:26 + | +LL | let bad32_suf: f32 = 1.123_456_789_f32; + | ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8_f32` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:38:21 + | +LL | let bad32_inf = 1.123_456_789_f32; + | ^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8_f32` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:40:22 + | +LL | let bad64: f64 = 0.123_456_789_012_345_67; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:41:26 + | +LL | let bad64_suf: f64 = 0.123_456_789_012_345_67f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66_f64` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:42:21 + | +LL | let bad64_inf = 0.123_456_789_012_345_67; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_012_345_66` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:48:36 + | +LL | let bad_vec32: Vec = vec![0.123_456_789]; + | ^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_79` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:49:36 + | +LL | let bad_vec64: Vec = vec![0.123_456_789_123_456_789]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `0.123_456_789_123_456_78` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:53:24 + | +LL | let bad_e32: f32 = 1.123_456_788_888e-10; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8e-10` + +error: float has excessive precision + --> $DIR/excessive_precision.rs:56:27 + | +LL | let bad_bige32: f32 = 1.123_456_788_888E-10; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.123_456_8E-10` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/exit1.rs b/src/tools/clippy/tests/ui/exit1.rs new file mode 100644 index 0000000000000..4eac6eb74672f --- /dev/null +++ b/src/tools/clippy/tests/ui/exit1.rs @@ -0,0 +1,15 @@ +#[warn(clippy::exit)] + +fn not_main() { + if true { + std::process::exit(4); + } +} + +fn main() { + if true { + std::process::exit(2); + }; + not_main(); + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit1.stderr b/src/tools/clippy/tests/ui/exit1.stderr new file mode 100644 index 0000000000000..a8d3956aa27a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit1.stderr @@ -0,0 +1,10 @@ +error: usage of `process::exit` + --> $DIR/exit1.rs:5:9 + | +LL | std::process::exit(4); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/exit2.rs b/src/tools/clippy/tests/ui/exit2.rs new file mode 100644 index 0000000000000..4b693ed7083f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit2.rs @@ -0,0 +1,13 @@ +#[warn(clippy::exit)] + +fn also_not_main() { + std::process::exit(3); +} + +fn main() { + if true { + std::process::exit(2); + }; + also_not_main(); + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/exit2.stderr b/src/tools/clippy/tests/ui/exit2.stderr new file mode 100644 index 0000000000000..7263e156a9d26 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit2.stderr @@ -0,0 +1,10 @@ +error: usage of `process::exit` + --> $DIR/exit2.rs:4:5 + | +LL | std::process::exit(3); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/exit3.rs b/src/tools/clippy/tests/ui/exit3.rs new file mode 100644 index 0000000000000..9dc0e1015a4f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/exit3.rs @@ -0,0 +1,8 @@ +#[warn(clippy::exit)] + +fn main() { + if true { + std::process::exit(2); + }; + std::process::exit(1); +} diff --git a/src/tools/clippy/tests/ui/expect.rs b/src/tools/clippy/tests/ui/expect.rs new file mode 100644 index 0000000000000..0bd4252c49aa5 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect.rs @@ -0,0 +1,16 @@ +#![warn(clippy::option_expect_used, clippy::result_expect_used)] + +fn expect_option() { + let opt = Some(0); + let _ = opt.expect(""); +} + +fn expect_result() { + let res: Result = Ok(0); + let _ = res.expect(""); +} + +fn main() { + expect_option(); + expect_result(); +} diff --git a/src/tools/clippy/tests/ui/expect.stderr b/src/tools/clippy/tests/ui/expect.stderr new file mode 100644 index 0000000000000..adf9f4f192191 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect.stderr @@ -0,0 +1,20 @@ +error: used `expect()` on `an Option` value + --> $DIR/expect.rs:5:13 + | +LL | let _ = opt.expect(""); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::option-expect-used` implied by `-D warnings` + = help: if this value is an `None`, it will panic + +error: used `expect()` on `a Result` value + --> $DIR/expect.rs:10:13 + | +LL | let _ = res.expect(""); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::result-expect-used` implied by `-D warnings` + = help: if this value is an `Err`, it will panic + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/expect_fun_call.fixed b/src/tools/clippy/tests/ui/expect_fun_call.fixed new file mode 100644 index 0000000000000..f3d8a941a92bc --- /dev/null +++ b/src/tools/clippy/tests/ui/expect_fun_call.fixed @@ -0,0 +1,94 @@ +// run-rustfix + +#![warn(clippy::expect_fun_call)] + +/// Checks implementation of the `EXPECT_FUN_CALL` lint + +fn main() { + struct Foo; + + impl Foo { + fn new() -> Self { + Foo + } + + fn expect(&self, msg: &str) { + panic!("{}", msg) + } + } + + let with_some = Some("value"); + with_some.expect("error"); + + let with_none: Option = None; + with_none.expect("error"); + + let error_code = 123_i32; + let with_none_and_format: Option = None; + with_none_and_format.unwrap_or_else(|| panic!("Error {}: fake error", error_code)); + + let with_none_and_as_str: Option = None; + with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code)); + + let with_ok: Result<(), ()> = Ok(()); + with_ok.expect("error"); + + let with_err: Result<(), ()> = Err(()); + with_err.expect("error"); + + let error_code = 123_i32; + let with_err_and_format: Result<(), ()> = Err(()); + with_err_and_format.unwrap_or_else(|_| panic!("Error {}: fake error", error_code)); + + let with_err_and_as_str: Result<(), ()> = Err(()); + with_err_and_as_str.unwrap_or_else(|_| panic!("Error {}: fake error", error_code)); + + let with_dummy_type = Foo::new(); + with_dummy_type.expect("another test string"); + + let with_dummy_type_and_format = Foo::new(); + with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_dummy_type_and_as_str = Foo::new(); + with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + //Issue #2937 + Some("foo").unwrap_or_else(|| panic!("{} {}", 1, 2)); + + //Issue #2979 - this should not lint + { + let msg = "bar"; + Some("foo").expect(msg); + } + + { + fn get_string() -> String { + "foo".to_string() + } + + fn get_static_str() -> &'static str { + "foo" + } + + fn get_non_static_str(_: &u32) -> &str { + "foo" + } + + Some("foo").unwrap_or_else(|| { panic!(get_string()) }); + Some("foo").unwrap_or_else(|| { panic!(get_string()) }); + Some("foo").unwrap_or_else(|| { panic!(get_string()) }); + + Some("foo").unwrap_or_else(|| { panic!(get_static_str()) }); + Some("foo").unwrap_or_else(|| { panic!(get_non_static_str(&0).to_string()) }); + } + + //Issue #3839 + Some(true).unwrap_or_else(|| panic!("key {}, {}", 1, 2)); + + //Issue #4912 - the receiver is a &Option + { + let opt = Some(1); + let opt_ref = &opt; + opt_ref.unwrap_or_else(|| panic!("{:?}", opt_ref)); + } +} diff --git a/src/tools/clippy/tests/ui/expect_fun_call.rs b/src/tools/clippy/tests/ui/expect_fun_call.rs new file mode 100644 index 0000000000000..60bbaa89d4282 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect_fun_call.rs @@ -0,0 +1,94 @@ +// run-rustfix + +#![warn(clippy::expect_fun_call)] + +/// Checks implementation of the `EXPECT_FUN_CALL` lint + +fn main() { + struct Foo; + + impl Foo { + fn new() -> Self { + Foo + } + + fn expect(&self, msg: &str) { + panic!("{}", msg) + } + } + + let with_some = Some("value"); + with_some.expect("error"); + + let with_none: Option = None; + with_none.expect("error"); + + let error_code = 123_i32; + let with_none_and_format: Option = None; + with_none_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_none_and_as_str: Option = None; + with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + let with_ok: Result<(), ()> = Ok(()); + with_ok.expect("error"); + + let with_err: Result<(), ()> = Err(()); + with_err.expect("error"); + + let error_code = 123_i32; + let with_err_and_format: Result<(), ()> = Err(()); + with_err_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_err_and_as_str: Result<(), ()> = Err(()); + with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + let with_dummy_type = Foo::new(); + with_dummy_type.expect("another test string"); + + let with_dummy_type_and_format = Foo::new(); + with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code)); + + let with_dummy_type_and_as_str = Foo::new(); + with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + + //Issue #2937 + Some("foo").expect(format!("{} {}", 1, 2).as_ref()); + + //Issue #2979 - this should not lint + { + let msg = "bar"; + Some("foo").expect(msg); + } + + { + fn get_string() -> String { + "foo".to_string() + } + + fn get_static_str() -> &'static str { + "foo" + } + + fn get_non_static_str(_: &u32) -> &str { + "foo" + } + + Some("foo").expect(&get_string()); + Some("foo").expect(get_string().as_ref()); + Some("foo").expect(get_string().as_str()); + + Some("foo").expect(get_static_str()); + Some("foo").expect(get_non_static_str(&0)); + } + + //Issue #3839 + Some(true).expect(&format!("key {}, {}", 1, 2)); + + //Issue #4912 - the receiver is a &Option + { + let opt = Some(1); + let opt_ref = &opt; + opt_ref.expect(&format!("{:?}", opt_ref)); + } +} diff --git a/src/tools/clippy/tests/ui/expect_fun_call.stderr b/src/tools/clippy/tests/ui/expect_fun_call.stderr new file mode 100644 index 0000000000000..a492e2df89d47 --- /dev/null +++ b/src/tools/clippy/tests/ui/expect_fun_call.stderr @@ -0,0 +1,76 @@ +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:28:26 + | +LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))` + | + = note: `-D clippy::expect-fun-call` implied by `-D warnings` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:31:26 + | +LL | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:41:25 + | +LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:44:25 + | +LL | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:56:17 + | +LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:77:21 + | +LL | Some("foo").expect(&get_string()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!(get_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:78:21 + | +LL | Some("foo").expect(get_string().as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!(get_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:79:21 + | +LL | Some("foo").expect(get_string().as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!(get_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:81:21 + | +LL | Some("foo").expect(get_static_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!(get_static_str()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:82:21 + | +LL | Some("foo").expect(get_non_static_str(&0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!(get_non_static_str(&0).to_string()) })` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:86:16 + | +LL | Some(true).expect(&format!("key {}, {}", 1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:92:17 + | +LL | opt_ref.expect(&format!("{:?}", opt_ref)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_counter_loop.rs b/src/tools/clippy/tests/ui/explicit_counter_loop.rs new file mode 100644 index 0000000000000..aa6ef162fe401 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_counter_loop.rs @@ -0,0 +1,147 @@ +#![warn(clippy::explicit_counter_loop)] + +fn main() { + let mut vec = vec![1, 2, 3, 4]; + let mut _index = 0; + for _v in &vec { + _index += 1 + } + + let mut _index = 1; + _index = 0; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + for _v in &mut vec { + _index += 1; + } + + let mut _index = 0; + for _v in vec { + _index += 1; + } +} + +mod issue_1219 { + pub fn test() { + // should not trigger the lint because variable is used after the loop #473 + let vec = vec![1, 2, 3]; + let mut index = 0; + for _v in &vec { + index += 1 + } + println!("index: {}", index); + + // should not trigger the lint because the count is conditional #1219 + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + if ch == 'a' { + continue; + } + count += 1; + println!("{}", count); + } + + // should not trigger the lint because the count is conditional + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + if ch == 'a' { + count += 1; + } + println!("{}", count); + } + + // should trigger the lint because the count is not conditional + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + count += 1; + if ch == 'a' { + continue; + } + println!("{}", count); + } + + // should trigger the lint because the count is not conditional + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + count += 1; + for i in 0..2 { + let _ = 123; + } + println!("{}", count); + } + + // should not trigger the lint because the count is incremented multiple times + let text = "banana"; + let mut count = 0; + for ch in text.chars() { + count += 1; + for i in 0..2 { + count += 1; + } + println!("{}", count); + } + } +} + +mod issue_3308 { + pub fn test() { + // should not trigger the lint because the count is incremented multiple times + let mut skips = 0; + let erasures = vec![]; + for i in 0..10 { + while erasures.contains(&(i + skips)) { + skips += 1; + } + println!("{}", skips); + } + + // should not trigger the lint because the count is incremented multiple times + let mut skips = 0; + for i in 0..10 { + let mut j = 0; + while j < 5 { + skips += 1; + j += 1; + } + println!("{}", skips); + } + + // should not trigger the lint because the count is incremented multiple times + let mut skips = 0; + for i in 0..10 { + for j in 0..5 { + skips += 1; + } + println!("{}", skips); + } + } +} + +mod issue_1670 { + pub fn test() { + let mut count = 0; + for _i in 3..10 { + count += 1; + } + } +} + +mod issue_4732 { + pub fn test() { + let slice = &[1, 2, 3]; + let mut index = 0; + + // should not trigger the lint because the count is used after the loop + for _v in slice { + index += 1 + } + let _closure = || println!("index: {}", index); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_counter_loop.stderr b/src/tools/clippy/tests/ui/explicit_counter_loop.stderr new file mode 100644 index 0000000000000..931af46efe663 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_counter_loop.stderr @@ -0,0 +1,46 @@ +error: the variable `_index` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:6:5 + | +LL | for _v in &vec { + | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()` + | + = note: `-D clippy::explicit-counter-loop` implied by `-D warnings` + +error: the variable `_index` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:12:5 + | +LL | for _v in &vec { + | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()` + +error: the variable `_index` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:17:5 + | +LL | for _v in &mut vec { + | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter_mut().enumerate()` + +error: the variable `_index` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:22:5 + | +LL | for _v in vec { + | ^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.into_iter().enumerate()` + +error: the variable `count` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:61:9 + | +LL | for ch in text.chars() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()` + +error: the variable `count` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:72:9 + | +LL | for ch in text.chars() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()` + +error: the variable `count` is used as a loop counter. + --> $DIR/explicit_counter_loop.rs:130:9 + | +LL | for _i in 3..10 { + | ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_write.fixed b/src/tools/clippy/tests/ui/explicit_write.fixed new file mode 100644 index 0000000000000..692d2ca675f91 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write.fixed @@ -0,0 +1,51 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn(clippy::explicit_write)] + +fn stdout() -> String { + String::new() +} + +fn stderr() -> String { + String::new() +} + +fn main() { + // these should warn + { + use std::io::Write; + print!("test"); + eprint!("test"); + println!("test"); + eprintln!("test"); + print!("test"); + eprint!("test"); + + // including newlines + println!("test\ntest"); + eprintln!("test\ntest"); + } + // these should not warn, different destination + { + use std::fmt::Write; + let mut s = String::new(); + write!(s, "test").unwrap(); + write!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + write!(stdout(), "test").unwrap(); + write!(stderr(), "test").unwrap(); + writeln!(stdout(), "test").unwrap(); + writeln!(stderr(), "test").unwrap(); + stdout().write_fmt(format_args!("test")).unwrap(); + stderr().write_fmt(format_args!("test")).unwrap(); + } + // these should not warn, no unwrap + { + use std::io::Write; + std::io::stdout().write_fmt(format_args!("test")).expect("no stdout"); + std::io::stderr().write_fmt(format_args!("test")).expect("no stderr"); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_write.rs b/src/tools/clippy/tests/ui/explicit_write.rs new file mode 100644 index 0000000000000..455c5ef55d05c --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write.rs @@ -0,0 +1,51 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn(clippy::explicit_write)] + +fn stdout() -> String { + String::new() +} + +fn stderr() -> String { + String::new() +} + +fn main() { + // these should warn + { + use std::io::Write; + write!(std::io::stdout(), "test").unwrap(); + write!(std::io::stderr(), "test").unwrap(); + writeln!(std::io::stdout(), "test").unwrap(); + writeln!(std::io::stderr(), "test").unwrap(); + std::io::stdout().write_fmt(format_args!("test")).unwrap(); + std::io::stderr().write_fmt(format_args!("test")).unwrap(); + + // including newlines + writeln!(std::io::stdout(), "test\ntest").unwrap(); + writeln!(std::io::stderr(), "test\ntest").unwrap(); + } + // these should not warn, different destination + { + use std::fmt::Write; + let mut s = String::new(); + write!(s, "test").unwrap(); + write!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + writeln!(s, "test").unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + s.write_fmt(format_args!("test")).unwrap(); + write!(stdout(), "test").unwrap(); + write!(stderr(), "test").unwrap(); + writeln!(stdout(), "test").unwrap(); + writeln!(stderr(), "test").unwrap(); + stdout().write_fmt(format_args!("test")).unwrap(); + stderr().write_fmt(format_args!("test")).unwrap(); + } + // these should not warn, no unwrap + { + use std::io::Write; + std::io::stdout().write_fmt(format_args!("test")).expect("no stdout"); + std::io::stderr().write_fmt(format_args!("test")).expect("no stderr"); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_write.stderr b/src/tools/clippy/tests/ui/explicit_write.stderr new file mode 100644 index 0000000000000..9feef9c0dc844 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write.stderr @@ -0,0 +1,52 @@ +error: use of `write!(stdout(), ...).unwrap()` + --> $DIR/explicit_write.rs:17:9 + | +LL | write!(std::io::stdout(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")` + | + = note: `-D clippy::explicit-write` implied by `-D warnings` + +error: use of `write!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:18:9 + | +LL | write!(std::io::stderr(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")` + +error: use of `writeln!(stdout(), ...).unwrap()` + --> $DIR/explicit_write.rs:19:9 + | +LL | writeln!(std::io::stdout(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test")` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:20:9 + | +LL | writeln!(std::io::stderr(), "test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test")` + +error: use of `stdout().write_fmt(...).unwrap()` + --> $DIR/explicit_write.rs:21:9 + | +LL | std::io::stdout().write_fmt(format_args!("test")).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")` + +error: use of `stderr().write_fmt(...).unwrap()` + --> $DIR/explicit_write.rs:22:9 + | +LL | std::io::stderr().write_fmt(format_args!("test")).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")` + +error: use of `writeln!(stdout(), ...).unwrap()` + --> $DIR/explicit_write.rs:25:9 + | +LL | writeln!(std::io::stdout(), "test/ntest").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test/ntest")` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:26:9 + | +LL | writeln!(std::io::stderr(), "test/ntest").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test/ntest")` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_write_non_rustfix.rs b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.rs new file mode 100644 index 0000000000000..f21e8ef935bd0 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.rs @@ -0,0 +1,8 @@ +#![allow(unused_imports, clippy::blacklisted_name)] +#![warn(clippy::explicit_write)] + +fn main() { + use std::io::Write; + let bar = "bar"; + writeln!(std::io::stderr(), "foo {}", bar).unwrap(); +} diff --git a/src/tools/clippy/tests/ui/explicit_write_non_rustfix.stderr b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.stderr new file mode 100644 index 0000000000000..77cadb99bb551 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_write_non_rustfix.stderr @@ -0,0 +1,10 @@ +error: use of `writeln!(stderr(), ...).unwrap()`. Consider using `eprintln!` instead + --> $DIR/explicit_write_non_rustfix.rs:7:5 + | +LL | writeln!(std::io::stderr(), "foo {}", bar).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::explicit-write` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs b/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs new file mode 100644 index 0000000000000..ddbf4e98c51ab --- /dev/null +++ b/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs @@ -0,0 +1,69 @@ +#![allow(unused, dead_code, clippy::needless_lifetimes, clippy::needless_pass_by_value)] +#![warn(clippy::extra_unused_lifetimes)] + +fn empty() {} + +fn used_lt<'a>(x: &'a u8) {} + +fn unused_lt<'a>(x: u8) {} + +fn unused_lt_transitive<'a, 'b: 'a>(x: &'b u8) { + // 'a is useless here since it's not directly bound +} + +fn lt_return<'a, 'b: 'a>(x: &'b u8) -> &'a u8 { + panic!() +} + +fn lt_return_only<'a>() -> &'a u8 { + panic!() +} + +fn unused_lt_blergh<'a>(x: Option>) {} + +trait Foo<'a> { + fn x(&self, a: &'a u8); +} + +impl<'a> Foo<'a> for u8 { + fn x(&self, a: &'a u8) {} +} + +struct Bar; + +impl Bar { + fn x<'a>(&self) {} +} + +// test for #489 (used lifetimes in bounds) +pub fn parse<'a, I: Iterator>(_it: &mut I) { + unimplemented!() +} +pub fn parse2<'a, I>(_it: &mut I) +where + I: Iterator, +{ + unimplemented!() +} + +struct X { + x: u32, +} + +impl X { + fn self_ref_with_lifetime<'a>(&'a self) {} + fn explicit_self_with_lifetime<'a>(self: &'a Self) {} +} + +// Methods implementing traits must have matching lifetimes +mod issue4291 { + trait BadTrait { + fn unused_lt<'a>(x: u8) {} + } + + impl BadTrait for () { + fn unused_lt<'a>(_x: u8) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr b/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr new file mode 100644 index 0000000000000..16bbb1c037d84 --- /dev/null +++ b/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr @@ -0,0 +1,28 @@ +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:8:14 + | +LL | fn unused_lt<'a>(x: u8) {} + | ^^ + | + = note: `-D clippy::extra-unused-lifetimes` implied by `-D warnings` + +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:10:25 + | +LL | fn unused_lt_transitive<'a, 'b: 'a>(x: &'b u8) { + | ^^ + +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:35:10 + | +LL | fn x<'a>(&self) {} + | ^^ + +error: this lifetime isn't used in the function definition + --> $DIR/extra_unused_lifetimes.rs:61:22 + | +LL | fn unused_lt<'a>(x: u8) {} + | ^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/fallible_impl_from.rs b/src/tools/clippy/tests/ui/fallible_impl_from.rs new file mode 100644 index 0000000000000..679f4a7dc357d --- /dev/null +++ b/src/tools/clippy/tests/ui/fallible_impl_from.rs @@ -0,0 +1,76 @@ +#![deny(clippy::fallible_impl_from)] + +// docs example +struct Foo(i32); +impl From for Foo { + fn from(s: String) -> Self { + Foo(s.parse().unwrap()) + } +} + +struct Valid(Vec); + +impl<'a> From<&'a str> for Valid { + fn from(s: &'a str) -> Valid { + Valid(s.to_owned().into_bytes()) + } +} +impl From for Valid { + fn from(i: usize) -> Valid { + Valid(Vec::with_capacity(i)) + } +} + +struct Invalid; + +impl From for Invalid { + fn from(i: usize) -> Invalid { + if i != 42 { + panic!(); + } + Invalid + } +} + +impl From> for Invalid { + fn from(s: Option) -> Invalid { + let s = s.unwrap(); + if !s.is_empty() { + panic!(42); + } else if s.parse::().unwrap() != 42 { + panic!("{:?}", s); + } + Invalid + } +} + +trait ProjStrTrait { + type ProjString; +} +impl ProjStrTrait for Box { + type ProjString = String; +} +impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { + fn from(s: &'a mut as ProjStrTrait>::ProjString) -> Invalid { + if s.parse::().ok().unwrap() != 42 { + panic!("{:?}", s); + } + Invalid + } +} + +struct Unreachable; + +impl From for Unreachable { + fn from(s: String) -> Unreachable { + if s.is_empty() { + return Unreachable; + } + match s.chars().next() { + Some(_) => Unreachable, + None => unreachable!(), // do not lint the unreachable macro + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/fallible_impl_from.stderr b/src/tools/clippy/tests/ui/fallible_impl_from.stderr new file mode 100644 index 0000000000000..ab976b947b356 --- /dev/null +++ b/src/tools/clippy/tests/ui/fallible_impl_from.stderr @@ -0,0 +1,93 @@ +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:5:1 + | +LL | / impl From for Foo { +LL | | fn from(s: String) -> Self { +LL | | Foo(s.parse().unwrap()) +LL | | } +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/fallible_impl_from.rs:1:9 + | +LL | #![deny(clippy::fallible_impl_from)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail. +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:7:13 + | +LL | Foo(s.parse().unwrap()) + | ^^^^^^^^^^^^^^^^^^ + +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:26:1 + | +LL | / impl From for Invalid { +LL | | fn from(i: usize) -> Invalid { +LL | | if i != 42 { +LL | | panic!(); +... | +LL | | } +LL | | } + | |_^ + | + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail. +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:29:13 + | +LL | panic!(); + | ^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:35:1 + | +LL | / impl From> for Invalid { +LL | | fn from(s: Option) -> Invalid { +LL | | let s = s.unwrap(); +LL | | if !s.is_empty() { +... | +LL | | } +LL | | } + | |_^ + | + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail. +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:37:17 + | +LL | let s = s.unwrap(); + | ^^^^^^^^^^ +LL | if !s.is_empty() { +LL | panic!(42); + | ^^^^^^^^^^^ +LL | } else if s.parse::().unwrap() != 42 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | panic!("{:?}", s); + | ^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: consider implementing `TryFrom` instead + --> $DIR/fallible_impl_from.rs:53:1 + | +LL | / impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { +LL | | fn from(s: &'a mut as ProjStrTrait>::ProjString) -> Invalid { +LL | | if s.parse::().ok().unwrap() != 42 { +LL | | panic!("{:?}", s); +... | +LL | | } +LL | | } + | |_^ + | + = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail. +note: potential failure(s) + --> $DIR/fallible_impl_from.rs:55:12 + | +LL | if s.parse::().ok().unwrap() != 42 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | panic!("{:?}", s); + | ^^^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/filetype_is_file.rs b/src/tools/clippy/tests/ui/filetype_is_file.rs new file mode 100644 index 0000000000000..5de8fe8cdd7b0 --- /dev/null +++ b/src/tools/clippy/tests/ui/filetype_is_file.rs @@ -0,0 +1,23 @@ +#![warn(clippy::filetype_is_file)] + +fn main() -> std::io::Result<()> { + use std::fs; + use std::ops::BitOr; + + // !filetype.is_dir() + if fs::metadata("foo.txt")?.file_type().is_file() { + // read file + } + + // positive of filetype.is_dir() + if !fs::metadata("foo.txt")?.file_type().is_file() { + // handle dir + } + + // false positive of filetype.is_dir() + if !fs::metadata("foo.txt")?.file_type().is_file().bitor(true) { + // ... + } + + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/filetype_is_file.stderr b/src/tools/clippy/tests/ui/filetype_is_file.stderr new file mode 100644 index 0000000000000..cd1e3ac37fe86 --- /dev/null +++ b/src/tools/clippy/tests/ui/filetype_is_file.stderr @@ -0,0 +1,27 @@ +error: `FileType::is_file()` only covers regular files + --> $DIR/filetype_is_file.rs:8:8 + | +LL | if fs::metadata("foo.txt")?.file_type().is_file() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::filetype-is-file` implied by `-D warnings` + = help: use `!FileType::is_dir()` instead + +error: `!FileType::is_file()` only denies regular files + --> $DIR/filetype_is_file.rs:13:8 + | +LL | if !fs::metadata("foo.txt")?.file_type().is_file() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `FileType::is_dir()` instead + +error: `FileType::is_file()` only covers regular files + --> $DIR/filetype_is_file.rs:18:9 + | +LL | if !fs::metadata("foo.txt")?.file_type().is_file().bitor(true) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `!FileType::is_dir()` instead + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/filter_map_next.rs b/src/tools/clippy/tests/ui/filter_map_next.rs new file mode 100644 index 0000000000000..f5d051be198ed --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next.rs @@ -0,0 +1,20 @@ +#![warn(clippy::all, clippy::pedantic)] + +fn main() { + let a = ["1", "lol", "3", "NaN", "5"]; + + let element: Option = a.iter().filter_map(|s| s.parse().ok()).next(); + assert_eq!(element, Some(1)); + + #[rustfmt::skip] + let _: Option = vec![1, 2, 3, 4, 5, 6] + .into_iter() + .filter_map(|x| { + if x == 2 { + Some(x * 2) + } else { + None + } + }) + .next(); +} diff --git a/src/tools/clippy/tests/ui/filter_map_next.stderr b/src/tools/clippy/tests/ui/filter_map_next.stderr new file mode 100644 index 0000000000000..d69ae212414f6 --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_map_next.stderr @@ -0,0 +1,24 @@ +error: called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(p)` instead. + --> $DIR/filter_map_next.rs:6:32 + | +LL | let element: Option = a.iter().filter_map(|s| s.parse().ok()).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::filter-map-next` implied by `-D warnings` + = note: replace `filter_map(|s| s.parse().ok()).next()` with `find_map(|s| s.parse().ok())` + +error: called `filter_map(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find_map(p)` instead. + --> $DIR/filter_map_next.rs:10:26 + | +LL | let _: Option = vec![1, 2, 3, 4, 5, 6] + | __________________________^ +LL | | .into_iter() +LL | | .filter_map(|x| { +LL | | if x == 2 { +... | +LL | | }) +LL | | .next(); + | |_______________^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/filter_methods.rs b/src/tools/clippy/tests/ui/filter_methods.rs new file mode 100644 index 0000000000000..ef434245fd70c --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_methods.rs @@ -0,0 +1,24 @@ +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::missing_docs_in_private_items)] + +fn main() { + let _: Vec<_> = vec![5; 6].into_iter().filter(|&x| x == 0).map(|x| x * 2).collect(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .filter(|&x| x == 0) + .flat_map(|x| x.checked_mul(2)) + .collect(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .filter_map(|x| x.checked_mul(2)) + .flat_map(|x| x.checked_mul(2)) + .collect(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .filter_map(|x| x.checked_mul(2)) + .map(|x| x.checked_mul(2)) + .collect(); +} diff --git a/src/tools/clippy/tests/ui/filter_methods.stderr b/src/tools/clippy/tests/ui/filter_methods.stderr new file mode 100644 index 0000000000000..84a957a374c6b --- /dev/null +++ b/src/tools/clippy/tests/ui/filter_methods.stderr @@ -0,0 +1,47 @@ +error: called `filter(p).map(q)` on an `Iterator` + --> $DIR/filter_methods.rs:5:21 + | +LL | let _: Vec<_> = vec![5; 6].into_iter().filter(|&x| x == 0).map(|x| x * 2).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::filter-map` implied by `-D warnings` + = help: this is more succinctly expressed by calling `.filter_map(..)` instead + +error: called `filter(p).flat_map(q)` on an `Iterator` + --> $DIR/filter_methods.rs:7:21 + | +LL | let _: Vec<_> = vec![5_i8; 6] + | _____________________^ +LL | | .into_iter() +LL | | .filter(|&x| x == 0) +LL | | .flat_map(|x| x.checked_mul(2)) + | |_______________________________________^ + | + = help: this is more succinctly expressed by calling `.flat_map(..)` and filtering by returning `iter::empty()` + +error: called `filter_map(p).flat_map(q)` on an `Iterator` + --> $DIR/filter_methods.rs:13:21 + | +LL | let _: Vec<_> = vec![5_i8; 6] + | _____________________^ +LL | | .into_iter() +LL | | .filter_map(|x| x.checked_mul(2)) +LL | | .flat_map(|x| x.checked_mul(2)) + | |_______________________________________^ + | + = help: this is more succinctly expressed by calling `.flat_map(..)` and filtering by returning `iter::empty()` + +error: called `filter_map(p).map(q)` on an `Iterator` + --> $DIR/filter_methods.rs:19:21 + | +LL | let _: Vec<_> = vec![5_i8; 6] + | _____________________^ +LL | | .into_iter() +LL | | .filter_map(|x| x.checked_mul(2)) +LL | | .map(|x| x.checked_mul(2)) + | |__________________________________^ + | + = help: this is more succinctly expressed by only calling `.filter_map(..)` instead + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/find_map.rs b/src/tools/clippy/tests/ui/find_map.rs new file mode 100644 index 0000000000000..c28cca144ca3f --- /dev/null +++ b/src/tools/clippy/tests/ui/find_map.rs @@ -0,0 +1,32 @@ +#![warn(clippy::all, clippy::pedantic)] + +#[derive(Debug, Copy, Clone)] +enum Flavor { + Chocolate, +} + +#[derive(Debug, Copy, Clone)] +enum Dessert { + Banana, + Pudding, + Cake(Flavor), +} + +fn main() { + let desserts_of_the_week = vec![Dessert::Banana, Dessert::Cake(Flavor::Chocolate), Dessert::Pudding]; + + let a = ["lol", "NaN", "2", "5", "Xunda"]; + + let _: Option = a.iter().find(|s| s.parse::().is_ok()).map(|s| s.parse().unwrap()); + + let _: Option = desserts_of_the_week + .iter() + .find(|dessert| match *dessert { + Dessert::Cake(_) => true, + _ => false, + }) + .map(|dessert| match *dessert { + Dessert::Cake(ref flavor) => *flavor, + _ => unreachable!(), + }); +} diff --git a/src/tools/clippy/tests/ui/find_map.stderr b/src/tools/clippy/tests/ui/find_map.stderr new file mode 100644 index 0000000000000..92f40fe6f1fb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/find_map.stderr @@ -0,0 +1,26 @@ +error: called `find(p).map(q)` on an `Iterator` + --> $DIR/find_map.rs:20:26 + | +LL | let _: Option = a.iter().find(|s| s.parse::().is_ok()).map(|s| s.parse().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::find-map` implied by `-D warnings` + = help: this is more succinctly expressed by calling `.find_map(..)` instead + +error: called `find(p).map(q)` on an `Iterator` + --> $DIR/find_map.rs:22:29 + | +LL | let _: Option = desserts_of_the_week + | _____________________________^ +LL | | .iter() +LL | | .find(|dessert| match *dessert { +LL | | Dessert::Cake(_) => true, +... | +LL | | _ => unreachable!(), +LL | | }); + | |__________^ + | + = help: this is more succinctly expressed by calling `.find_map(..)` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/float_arithmetic.rs b/src/tools/clippy/tests/ui/float_arithmetic.rs new file mode 100644 index 0000000000000..60fa7569eb9dd --- /dev/null +++ b/src/tools/clippy/tests/ui/float_arithmetic.rs @@ -0,0 +1,52 @@ +#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::op_ref +)] + +#[rustfmt::skip] +fn main() { + let mut f = 1.0f32; + + f * 2.0; + + 1.0 + f; + f * 2.0; + f / 2.0; + f - 2.0 * 4.2; + -f; + + f += 1.0; + f -= 1.0; + f *= 2.0; + f /= 2.0; +} + +// also warn about floating point arith with references involved + +pub fn float_arith_ref() { + 3.1_f32 + &1.2_f32; + &3.4_f32 + 1.5_f32; + &3.5_f32 + &1.3_f32; +} + +pub fn float_foo(f: &f32) -> f32 { + let a = 5.1; + a + f +} + +pub fn float_bar(f1: &f32, f2: &f32) -> f32 { + f1 + f2 +} + +pub fn float_baz(f1: f32, f2: &f32) -> f32 { + f1 + f2 +} + +pub fn float_qux(f1: f32, f2: f32) -> f32 { + (&f1 + &f2) +} diff --git a/src/tools/clippy/tests/ui/float_arithmetic.stderr b/src/tools/clippy/tests/ui/float_arithmetic.stderr new file mode 100644 index 0000000000000..1ceffb35beede --- /dev/null +++ b/src/tools/clippy/tests/ui/float_arithmetic.stderr @@ -0,0 +1,106 @@ +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:15:5 + | +LL | f * 2.0; + | ^^^^^^^ + | + = note: `-D clippy::float-arithmetic` implied by `-D warnings` + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:17:5 + | +LL | 1.0 + f; + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:18:5 + | +LL | f * 2.0; + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:19:5 + | +LL | f / 2.0; + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:20:5 + | +LL | f - 2.0 * 4.2; + | ^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:21:5 + | +LL | -f; + | ^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:23:5 + | +LL | f += 1.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:24:5 + | +LL | f -= 1.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:25:5 + | +LL | f *= 2.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:26:5 + | +LL | f /= 2.0; + | ^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:32:5 + | +LL | 3.1_f32 + &1.2_f32; + | ^^^^^^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:33:5 + | +LL | &3.4_f32 + 1.5_f32; + | ^^^^^^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:34:5 + | +LL | &3.5_f32 + &1.3_f32; + | ^^^^^^^^^^^^^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:39:5 + | +LL | a + f + | ^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:43:5 + | +LL | f1 + f2 + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:47:5 + | +LL | f1 + f2 + | ^^^^^^^ + +error: floating-point arithmetic detected + --> $DIR/float_arithmetic.rs:51:5 + | +LL | (&f1 + &f2) + | ^^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/float_cmp.rs b/src/tools/clippy/tests/ui/float_cmp.rs new file mode 100644 index 0000000000000..9fa0e5f5c079b --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp.rs @@ -0,0 +1,119 @@ +#![warn(clippy::float_cmp)] +#![allow( + unused, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::cast_lossless, + clippy::many_single_char_names +)] + +use std::ops::Add; + +const ZERO: f32 = 0.0; +const ONE: f32 = ZERO + 1.0; + +fn twice(x: T) -> T +where + T: Add + Copy, +{ + x + x +} + +fn eq_fl(x: f32, y: f32) -> bool { + if x.is_nan() { + y.is_nan() + } else { + x == y + } // no error, inside "eq" fn +} + +fn fl_eq(x: f32, y: f32) -> bool { + if x.is_nan() { + y.is_nan() + } else { + x == y + } // no error, inside "eq" fn +} + +struct X { + val: f32, +} + +impl PartialEq for X { + fn eq(&self, o: &X) -> bool { + if self.val.is_nan() { + o.val.is_nan() + } else { + self.val == o.val // no error, inside "eq" fn + } + } +} + +fn main() { + ZERO == 0f32; //no error, comparison with zero is ok + 1.0f32 != f32::INFINITY; // also comparison with infinity + 1.0f32 != f32::NEG_INFINITY; // and negative infinity + ZERO == 0.0; //no error, comparison with zero is ok + ZERO + ZERO != 1.0; //no error, comparison with zero is ok + + ONE == 1f32; + ONE == 1.0 + 0.0; + ONE + ONE == ZERO + ONE + ONE; + ONE != 2.0; + ONE != 0.0; // no error, comparison with zero is ok + twice(ONE) != ONE; + ONE as f64 != 2.0; + ONE as f64 != 0.0; // no error, comparison with zero is ok + + let x: f64 = 1.0; + + x == 1.0; + x != 0f64; // no error, comparison with zero is ok + + twice(x) != twice(ONE as f64); + + x < 0.0; // no errors, lower or greater comparisons need no fuzzyness + x > 0.0; + x <= 0.0; + x >= 0.0; + + let xs: [f32; 1] = [0.0]; + let a: *const f32 = xs.as_ptr(); + let b: *const f32 = xs.as_ptr(); + + assert_eq!(a, b); // no errors + + const ZERO_ARRAY: [f32; 2] = [0.0, 0.0]; + const NON_ZERO_ARRAY: [f32; 2] = [0.0, 0.1]; + + let i = 0; + let j = 1; + + ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; // ok, because lhs is zero regardless of i + NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; + + let a1: [f32; 1] = [0.0]; + let a2: [f32; 1] = [1.1]; + + a1 == a2; + a1[0] == a2[0]; + + // no errors - comparing signums is ok + let x32 = 3.21f32; + 1.23f32.signum() == x32.signum(); + 1.23f32.signum() == -(x32.signum()); + 1.23f32.signum() == 3.21f32.signum(); + + 1.23f32.signum() != x32.signum(); + 1.23f32.signum() != -(x32.signum()); + 1.23f32.signum() != 3.21f32.signum(); + + let x64 = 3.21f64; + 1.23f64.signum() == x64.signum(); + 1.23f64.signum() == -(x64.signum()); + 1.23f64.signum() == 3.21f64.signum(); + + 1.23f64.signum() != x64.signum(); + 1.23f64.signum() != -(x64.signum()); + 1.23f64.signum() != 3.21f64.signum(); +} diff --git a/src/tools/clippy/tests/ui/float_cmp.stderr b/src/tools/clippy/tests/ui/float_cmp.stderr new file mode 100644 index 0000000000000..2d454e8e70de5 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp.stderr @@ -0,0 +1,51 @@ +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:65:5 + | +LL | ONE as f64 != 2.0; + | ^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(ONE as f64 - 2.0).abs() > error` + | + = note: `-D clippy::float-cmp` implied by `-D warnings` + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:70:5 + | +LL | x == 1.0; + | ^^^^^^^^ help: consider comparing them within some error: `(x - 1.0).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:73:5 + | +LL | twice(x) != twice(ONE as f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(twice(x) - twice(ONE as f64)).abs() > error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:93:5 + | +LL | NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(NON_ZERO_ARRAY[i] - NON_ZERO_ARRAY[j]).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` arrays + --> $DIR/float_cmp.rs:98:5 + | +LL | a1 == a2; + | ^^^^^^^^ + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` + --> $DIR/float_cmp.rs:99:5 + | +LL | a1[0] == a2[0]; + | ^^^^^^^^^^^^^^ help: consider comparing them within some error: `(a1[0] - a2[0]).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/float_cmp_const.rs b/src/tools/clippy/tests/ui/float_cmp_const.rs new file mode 100644 index 0000000000000..dfc025558a2f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp_const.rs @@ -0,0 +1,62 @@ +// does not test any rustfixable lints + +#![warn(clippy::float_cmp_const)] +#![allow(clippy::float_cmp)] +#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)] + +const ONE: f32 = 1.0; +const TWO: f32 = 2.0; + +fn eq_one(x: f32) -> bool { + if x.is_nan() { + false + } else { + x == ONE + } // no error, inside "eq" fn +} + +fn main() { + // has errors + 1f32 == ONE; + TWO == ONE; + TWO != ONE; + ONE + ONE == TWO; + let x = 1; + x as f32 == ONE; + + let v = 0.9; + v == ONE; + v != ONE; + + // no errors, lower than or greater than comparisons + v < ONE; + v > ONE; + v <= ONE; + v >= ONE; + + // no errors, zero and infinity values + ONE != 0f32; + TWO == 0f32; + ONE != f32::INFINITY; + ONE == f32::NEG_INFINITY; + + // no errors, but will warn clippy::float_cmp if '#![allow(float_cmp)]' above is removed + let w = 1.1; + v == w; + v != w; + v == 1.0; + v != 1.0; + + const ZERO_ARRAY: [f32; 3] = [0.0, 0.0, 0.0]; + const ZERO_INF_ARRAY: [f32; 3] = [0.0, ::std::f32::INFINITY, ::std::f32::NEG_INFINITY]; + const NON_ZERO_ARRAY: [f32; 3] = [0.0, 0.1, 0.2]; + const NON_ZERO_ARRAY2: [f32; 3] = [0.2, 0.1, 0.0]; + + // no errors, zero and infinity values + NON_ZERO_ARRAY[0] == NON_ZERO_ARRAY2[1]; // lhs is 0.0 + ZERO_ARRAY == NON_ZERO_ARRAY; // lhs is all zeros + ZERO_INF_ARRAY == NON_ZERO_ARRAY; // lhs is all zeros or infinities + + // has errors + NON_ZERO_ARRAY == NON_ZERO_ARRAY2; +} diff --git a/src/tools/clippy/tests/ui/float_cmp_const.stderr b/src/tools/clippy/tests/ui/float_cmp_const.stderr new file mode 100644 index 0000000000000..19dc4a284b726 --- /dev/null +++ b/src/tools/clippy/tests/ui/float_cmp_const.stderr @@ -0,0 +1,67 @@ +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:20:5 + | +LL | 1f32 == ONE; + | ^^^^^^^^^^^ help: consider comparing them within some error: `(1f32 - ONE).abs() < error` + | + = note: `-D clippy::float-cmp-const` implied by `-D warnings` + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:21:5 + | +LL | TWO == ONE; + | ^^^^^^^^^^ help: consider comparing them within some error: `(TWO - ONE).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:22:5 + | +LL | TWO != ONE; + | ^^^^^^^^^^ help: consider comparing them within some error: `(TWO - ONE).abs() > error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:23:5 + | +LL | ONE + ONE == TWO; + | ^^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(ONE + ONE - TWO).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:25:5 + | +LL | x as f32 == ONE; + | ^^^^^^^^^^^^^^^ help: consider comparing them within some error: `(x as f32 - ONE).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:28:5 + | +LL | v == ONE; + | ^^^^^^^^ help: consider comparing them within some error: `(v - ONE).abs() < error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant + --> $DIR/float_cmp_const.rs:29:5 + | +LL | v != ONE; + | ^^^^^^^^ help: consider comparing them within some error: `(v - ONE).abs() > error` + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: strict comparison of `f32` or `f64` constant arrays + --> $DIR/float_cmp_const.rs:61:5 + | +LL | NON_ZERO_ARRAY == NON_ZERO_ARRAY2; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_abs.fixed b/src/tools/clippy/tests/ui/floating_point_abs.fixed new file mode 100644 index 0000000000000..b623e4988e7d4 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_abs.fixed @@ -0,0 +1,98 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +struct A { + a: f64, + b: f64, +} + +fn fake_abs1(num: f64) -> f64 { + num.abs() +} + +fn fake_abs2(num: f64) -> f64 { + num.abs() +} + +fn fake_abs3(a: A) -> f64 { + a.a.abs() +} + +fn fake_abs4(num: f64) -> f64 { + num.abs() +} + +fn fake_abs5(a: A) -> f64 { + a.a.abs() +} + +fn fake_nabs1(num: f64) -> f64 { + -num.abs() +} + +fn fake_nabs2(num: f64) -> f64 { + -num.abs() +} + +fn fake_nabs3(a: A) -> A { + A { + a: -a.a.abs(), + b: a.b, + } +} + +fn not_fake_abs1(num: f64) -> f64 { + if num > 0.0 { + num + } else { + -num - 1f64 + } +} + +fn not_fake_abs2(num: f64) -> f64 { + if num > 0.0 { + num + 1.0 + } else { + -(num + 1.0) + } +} + +fn not_fake_abs3(num1: f64, num2: f64) -> f64 { + if num1 > 0.0 { + num2 + } else { + -num2 + } +} + +fn not_fake_abs4(a: A) -> f64 { + if a.a > 0.0 { + a.b + } else { + -a.b + } +} + +fn not_fake_abs5(a: A) -> f64 { + if a.a > 0.0 { + a.a + } else { + -a.b + } +} + +fn main() { + fake_abs1(5.0); + fake_abs2(5.0); + fake_abs3(A { a: 5.0, b: 5.0 }); + fake_abs4(5.0); + fake_abs5(A { a: 5.0, b: 5.0 }); + fake_nabs1(5.0); + fake_nabs2(5.0); + fake_nabs3(A { a: 5.0, b: 5.0 }); + not_fake_abs1(5.0); + not_fake_abs2(5.0); + not_fake_abs3(5.0, 5.0); + not_fake_abs4(A { a: 5.0, b: 5.0 }); + not_fake_abs5(A { a: 5.0, b: 5.0 }); +} diff --git a/src/tools/clippy/tests/ui/floating_point_abs.rs b/src/tools/clippy/tests/ui/floating_point_abs.rs new file mode 100644 index 0000000000000..cbf9c94e41e6a --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_abs.rs @@ -0,0 +1,126 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +struct A { + a: f64, + b: f64, +} + +fn fake_abs1(num: f64) -> f64 { + if num >= 0.0 { + num + } else { + -num + } +} + +fn fake_abs2(num: f64) -> f64 { + if 0.0 < num { + num + } else { + -num + } +} + +fn fake_abs3(a: A) -> f64 { + if a.a > 0.0 { + a.a + } else { + -a.a + } +} + +fn fake_abs4(num: f64) -> f64 { + if 0.0 >= num { + -num + } else { + num + } +} + +fn fake_abs5(a: A) -> f64 { + if a.a < 0.0 { + -a.a + } else { + a.a + } +} + +fn fake_nabs1(num: f64) -> f64 { + if num < 0.0 { + num + } else { + -num + } +} + +fn fake_nabs2(num: f64) -> f64 { + if 0.0 >= num { + num + } else { + -num + } +} + +fn fake_nabs3(a: A) -> A { + A { + a: if a.a >= 0.0 { -a.a } else { a.a }, + b: a.b, + } +} + +fn not_fake_abs1(num: f64) -> f64 { + if num > 0.0 { + num + } else { + -num - 1f64 + } +} + +fn not_fake_abs2(num: f64) -> f64 { + if num > 0.0 { + num + 1.0 + } else { + -(num + 1.0) + } +} + +fn not_fake_abs3(num1: f64, num2: f64) -> f64 { + if num1 > 0.0 { + num2 + } else { + -num2 + } +} + +fn not_fake_abs4(a: A) -> f64 { + if a.a > 0.0 { + a.b + } else { + -a.b + } +} + +fn not_fake_abs5(a: A) -> f64 { + if a.a > 0.0 { + a.a + } else { + -a.b + } +} + +fn main() { + fake_abs1(5.0); + fake_abs2(5.0); + fake_abs3(A { a: 5.0, b: 5.0 }); + fake_abs4(5.0); + fake_abs5(A { a: 5.0, b: 5.0 }); + fake_nabs1(5.0); + fake_nabs2(5.0); + fake_nabs3(A { a: 5.0, b: 5.0 }); + not_fake_abs1(5.0); + not_fake_abs2(5.0); + not_fake_abs3(5.0, 5.0); + not_fake_abs4(A { a: 5.0, b: 5.0 }); + not_fake_abs5(A { a: 5.0, b: 5.0 }); +} diff --git a/src/tools/clippy/tests/ui/floating_point_abs.stderr b/src/tools/clippy/tests/ui/floating_point_abs.stderr new file mode 100644 index 0000000000000..74a71f2ca7c57 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_abs.stderr @@ -0,0 +1,80 @@ +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:10:5 + | +LL | / if num >= 0.0 { +LL | | num +LL | | } else { +LL | | -num +LL | | } + | |_____^ help: try: `num.abs()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:18:5 + | +LL | / if 0.0 < num { +LL | | num +LL | | } else { +LL | | -num +LL | | } + | |_____^ help: try: `num.abs()` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:26:5 + | +LL | / if a.a > 0.0 { +LL | | a.a +LL | | } else { +LL | | -a.a +LL | | } + | |_____^ help: try: `a.a.abs()` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:34:5 + | +LL | / if 0.0 >= num { +LL | | -num +LL | | } else { +LL | | num +LL | | } + | |_____^ help: try: `num.abs()` + +error: manual implementation of `abs` method + --> $DIR/floating_point_abs.rs:42:5 + | +LL | / if a.a < 0.0 { +LL | | -a.a +LL | | } else { +LL | | a.a +LL | | } + | |_____^ help: try: `a.a.abs()` + +error: manual implementation of negation of `abs` method + --> $DIR/floating_point_abs.rs:50:5 + | +LL | / if num < 0.0 { +LL | | num +LL | | } else { +LL | | -num +LL | | } + | |_____^ help: try: `-num.abs()` + +error: manual implementation of negation of `abs` method + --> $DIR/floating_point_abs.rs:58:5 + | +LL | / if 0.0 >= num { +LL | | num +LL | | } else { +LL | | -num +LL | | } + | |_____^ help: try: `-num.abs()` + +error: manual implementation of negation of `abs` method + --> $DIR/floating_point_abs.rs:67:12 + | +LL | a: if a.a >= 0.0 { -a.a } else { a.a }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-a.a.abs()` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_exp.fixed b/src/tools/clippy/tests/ui/floating_point_exp.fixed new file mode 100644 index 0000000000000..ae7805fdf018e --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_exp.fixed @@ -0,0 +1,18 @@ +// run-rustfix +#![warn(clippy::imprecise_flops)] + +fn main() { + let x = 2f32; + let _ = x.exp_m1(); + let _ = x.exp_m1() + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; + + let x = 2f64; + let _ = x.exp_m1(); + let _ = x.exp_m1() + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; +} diff --git a/src/tools/clippy/tests/ui/floating_point_exp.rs b/src/tools/clippy/tests/ui/floating_point_exp.rs new file mode 100644 index 0000000000000..27e0b9bcbc937 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_exp.rs @@ -0,0 +1,18 @@ +// run-rustfix +#![warn(clippy::imprecise_flops)] + +fn main() { + let x = 2f32; + let _ = x.exp() - 1.0; + let _ = x.exp() - 1.0 + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; + + let x = 2f64; + let _ = x.exp() - 1.0; + let _ = x.exp() - 1.0 + 2.0; + // Cases where the lint shouldn't be applied + let _ = x.exp() - 2.0; + let _ = x.exp() - 1.0 * 2.0; +} diff --git a/src/tools/clippy/tests/ui/floating_point_exp.stderr b/src/tools/clippy/tests/ui/floating_point_exp.stderr new file mode 100644 index 0000000000000..5cd999ad47cdd --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_exp.stderr @@ -0,0 +1,28 @@ +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:6:13 + | +LL | let _ = x.exp() - 1.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:7:13 + | +LL | let _ = x.exp() - 1.0 + 2.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:13:13 + | +LL | let _ = x.exp() - 1.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + +error: (e.pow(x) - 1) can be computed more accurately + --> $DIR/floating_point_exp.rs:14:13 + | +LL | let _ = x.exp() - 1.0 + 2.0; + | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_log.fixed b/src/tools/clippy/tests/ui/floating_point_log.fixed new file mode 100644 index 0000000000000..42c5e5d2bae24 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_log.fixed @@ -0,0 +1,58 @@ +// run-rustfix +#![allow(dead_code, clippy::double_parens)] +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +const TWO: f32 = 2.0; +const E: f32 = std::f32::consts::E; + +fn check_log_base() { + let x = 1f32; + let _ = x.log2(); + let _ = x.log10(); + let _ = x.ln(); + let _ = x.log2(); + let _ = x.ln(); + + let x = 1f64; + let _ = x.log2(); + let _ = x.log10(); + let _ = x.ln(); +} + +fn check_ln1p() { + let x = 1f32; + let _ = 2.0f32.ln_1p(); + let _ = 2.0f32.ln_1p(); + let _ = x.ln_1p(); + let _ = (x / 2.0).ln_1p(); + let _ = x.powi(2).ln_1p(); + let _ = (x.powi(2) / 2.0).ln_1p(); + let _ = ((std::f32::consts::E - 1.0)).ln_1p(); + let _ = x.ln_1p(); + let _ = x.powi(2).ln_1p(); + let _ = (x + 2.0).ln_1p(); + let _ = (x / 2.0).ln_1p(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); + + let x = 1f64; + let _ = 2.0f64.ln_1p(); + let _ = 2.0f64.ln_1p(); + let _ = x.ln_1p(); + let _ = (x / 2.0).ln_1p(); + let _ = x.powi(2).ln_1p(); + let _ = x.ln_1p(); + let _ = x.powi(2).ln_1p(); + let _ = (x + 2.0).ln_1p(); + let _ = (x / 2.0).ln_1p(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/floating_point_log.rs b/src/tools/clippy/tests/ui/floating_point_log.rs new file mode 100644 index 0000000000000..8be0d9ad56fc3 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_log.rs @@ -0,0 +1,58 @@ +// run-rustfix +#![allow(dead_code, clippy::double_parens)] +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +const TWO: f32 = 2.0; +const E: f32 = std::f32::consts::E; + +fn check_log_base() { + let x = 1f32; + let _ = x.log(2f32); + let _ = x.log(10f32); + let _ = x.log(std::f32::consts::E); + let _ = x.log(TWO); + let _ = x.log(E); + + let x = 1f64; + let _ = x.log(2f64); + let _ = x.log(10f64); + let _ = x.log(std::f64::consts::E); +} + +fn check_ln1p() { + let x = 1f32; + let _ = (1f32 + 2.).ln(); + let _ = (1f32 + 2.0).ln(); + let _ = (1.0 + x).ln(); + let _ = (1.0 + x / 2.0).ln(); + let _ = (1.0 + x.powi(2)).ln(); + let _ = (1.0 + x.powi(2) / 2.0).ln(); + let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); + let _ = (x + 1.0).ln(); + let _ = (x.powi(2) + 1.0).ln(); + let _ = (x + 2.0 + 1.0).ln(); + let _ = (x / 2.0 + 1.0).ln(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); + + let x = 1f64; + let _ = (1f64 + 2.).ln(); + let _ = (1f64 + 2.0).ln(); + let _ = (1.0 + x).ln(); + let _ = (1.0 + x / 2.0).ln(); + let _ = (1.0 + x.powi(2)).ln(); + let _ = (x + 1.0).ln(); + let _ = (x.powi(2) + 1.0).ln(); + let _ = (x + 2.0 + 1.0).ln(); + let _ = (x / 2.0 + 1.0).ln(); + // Cases where the lint shouldn't be applied + let _ = (1.0 + x + 2.0).ln(); + let _ = (x + 1.0 + 2.0).ln(); + let _ = (x + 1.0 / 2.0).ln(); + let _ = (1.0 + x - 2.0).ln(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/floating_point_log.stderr b/src/tools/clippy/tests/ui/floating_point_log.stderr new file mode 100644 index 0000000000000..943fbdb0b8323 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_log.stderr @@ -0,0 +1,174 @@ +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:10:13 + | +LL | let _ = x.log(2f32); + | ^^^^^^^^^^^ help: consider using: `x.log2()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:11:13 + | +LL | let _ = x.log(10f32); + | ^^^^^^^^^^^^ help: consider using: `x.log10()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:12:13 + | +LL | let _ = x.log(std::f32::consts::E); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:13:13 + | +LL | let _ = x.log(TWO); + | ^^^^^^^^^^ help: consider using: `x.log2()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:14:13 + | +LL | let _ = x.log(E); + | ^^^^^^^^ help: consider using: `x.ln()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:17:13 + | +LL | let _ = x.log(2f64); + | ^^^^^^^^^^^ help: consider using: `x.log2()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:18:13 + | +LL | let _ = x.log(10f64); + | ^^^^^^^^^^^^ help: consider using: `x.log10()` + +error: logarithm for bases 2, 10 and e can be computed more accurately + --> $DIR/floating_point_log.rs:19:13 + | +LL | let _ = x.log(std::f64::consts::E); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:24:13 + | +LL | let _ = (1f32 + 2.).ln(); + | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:25:13 + | +LL | let _ = (1f32 + 2.0).ln(); + | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:26:13 + | +LL | let _ = (1.0 + x).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:27:13 + | +LL | let _ = (1.0 + x / 2.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:28:13 + | +LL | let _ = (1.0 + x.powi(2)).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:29:13 + | +LL | let _ = (1.0 + x.powi(2) / 2.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x.powi(2) / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:30:13 + | +LL | let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `((std::f32::consts::E - 1.0)).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:31:13 + | +LL | let _ = (x + 1.0).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:32:13 + | +LL | let _ = (x.powi(2) + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:33:13 + | +LL | let _ = (x + 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:34:13 + | +LL | let _ = (x / 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:42:13 + | +LL | let _ = (1f64 + 2.).ln(); + | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:43:13 + | +LL | let _ = (1f64 + 2.0).ln(); + | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:44:13 + | +LL | let _ = (1.0 + x).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:45:13 + | +LL | let _ = (1.0 + x / 2.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:46:13 + | +LL | let _ = (1.0 + x.powi(2)).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:47:13 + | +LL | let _ = (x + 1.0).ln(); + | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:48:13 + | +LL | let _ = (x.powi(2) + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:49:13 + | +LL | let _ = (x + 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` + +error: ln(1 + x) can be computed more accurately + --> $DIR/floating_point_log.rs:50:13 + | +LL | let _ = (x / 2.0 + 1.0).ln(); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` + +error: aborting due to 28 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.fixed b/src/tools/clippy/tests/ui/floating_point_mul_add.fixed new file mode 100644 index 0000000000000..e343c37740da5 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.fixed @@ -0,0 +1,21 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let a: f64 = 1234.567; + let b: f64 = 45.67834; + let c: f64 = 0.0004; + let d: f64 = 0.0001; + + let _ = a.mul_add(b, c); + let _ = a.mul_add(b, c); + let _ = 2.0f64.mul_add(4.0, a); + let _ = 2.0f64.mul_add(4., a); + + let _ = a.mul_add(b, c); + let _ = a.mul_add(b, c); + let _ = (a * b).mul_add(c, d); + + let _ = a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c)) + c; + let _ = 1234.567_f64.mul_add(45.67834_f64, 0.0004_f64); +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.rs b/src/tools/clippy/tests/ui/floating_point_mul_add.rs new file mode 100644 index 0000000000000..810f929c8568b --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.rs @@ -0,0 +1,21 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops)] + +fn main() { + let a: f64 = 1234.567; + let b: f64 = 45.67834; + let c: f64 = 0.0004; + let d: f64 = 0.0001; + + let _ = a * b + c; + let _ = c + a * b; + let _ = a + 2.0 * 4.0; + let _ = a + 2. * 4.; + + let _ = (a * b) + c; + let _ = c + (a * b); + let _ = a * b * c + d; + + let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c; + let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64; +} diff --git a/src/tools/clippy/tests/ui/floating_point_mul_add.stderr b/src/tools/clippy/tests/ui/floating_point_mul_add.stderr new file mode 100644 index 0000000000000..2dfbf562d15fc --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_mul_add.stderr @@ -0,0 +1,58 @@ +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:10:13 + | +LL | let _ = a * b + c; + | ^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:11:13 + | +LL | let _ = c + a * b; + | ^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:12:13 + | +LL | let _ = a + 2.0 * 4.0; + | ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4.0, a)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:13:13 + | +LL | let _ = a + 2. * 4.; + | ^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4., a)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:15:13 + | +LL | let _ = (a * b) + c; + | ^^^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:16:13 + | +LL | let _ = c + (a * b); + | ^^^^^^^^^^^ help: consider using: `a.mul_add(b, c)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:17:13 + | +LL | let _ = a * b * c + d; + | ^^^^^^^^^^^^^ help: consider using: `(a * b).mul_add(c, d)` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:19:13 + | +LL | let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c))` + +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_mul_add.rs:20:13 + | +LL | let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1234.567_f64.mul_add(45.67834_f64, 0.0004_f64)` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/floating_point_powf.fixed b/src/tools/clippy/tests/ui/floating_point_powf.fixed new file mode 100644 index 0000000000000..78a9d44829bb1 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powf.fixed @@ -0,0 +1,42 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +fn main() { + let x = 3f32; + let _ = x.exp2(); + let _ = 3.1f32.exp2(); + let _ = (-3.1f32).exp2(); + let _ = x.exp(); + let _ = 3.1f32.exp(); + let _ = (-3.1f32).exp(); + let _ = x.sqrt(); + let _ = x.cbrt(); + let _ = x.powi(2); + let _ = x.powi(-2); + let _ = x.powi(16_777_215); + let _ = x.powi(-16_777_215); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(16_777_216.0); + let _ = x.powf(-16_777_216.0); + + let x = 3f64; + let _ = x.exp2(); + let _ = 3.1f64.exp2(); + let _ = (-3.1f64).exp2(); + let _ = x.exp(); + let _ = 3.1f64.exp(); + let _ = (-3.1f64).exp(); + let _ = x.sqrt(); + let _ = x.cbrt(); + let _ = x.powi(2); + let _ = x.powi(-2); + let _ = x.powi(-2_147_483_648); + let _ = x.powi(2_147_483_647); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(-2_147_483_649.0); + let _ = x.powf(2_147_483_648.0); +} diff --git a/src/tools/clippy/tests/ui/floating_point_powf.rs b/src/tools/clippy/tests/ui/floating_point_powf.rs new file mode 100644 index 0000000000000..dbc1cac5cb431 --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powf.rs @@ -0,0 +1,42 @@ +// run-rustfix +#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)] + +fn main() { + let x = 3f32; + let _ = 2f32.powf(x); + let _ = 2f32.powf(3.1); + let _ = 2f32.powf(-3.1); + let _ = std::f32::consts::E.powf(x); + let _ = std::f32::consts::E.powf(3.1); + let _ = std::f32::consts::E.powf(-3.1); + let _ = x.powf(1.0 / 2.0); + let _ = x.powf(1.0 / 3.0); + let _ = x.powf(2.0); + let _ = x.powf(-2.0); + let _ = x.powf(16_777_215.0); + let _ = x.powf(-16_777_215.0); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(16_777_216.0); + let _ = x.powf(-16_777_216.0); + + let x = 3f64; + let _ = 2f64.powf(x); + let _ = 2f64.powf(3.1); + let _ = 2f64.powf(-3.1); + let _ = std::f64::consts::E.powf(x); + let _ = std::f64::consts::E.powf(3.1); + let _ = std::f64::consts::E.powf(-3.1); + let _ = x.powf(1.0 / 2.0); + let _ = x.powf(1.0 / 3.0); + let _ = x.powf(2.0); + let _ = x.powf(-2.0); + let _ = x.powf(-2_147_483_648.0); + let _ = x.powf(2_147_483_647.0); + // Cases where the lint shouldn't be applied + let _ = x.powf(2.1); + let _ = x.powf(-2.1); + let _ = x.powf(-2_147_483_649.0); + let _ = x.powf(2_147_483_648.0); +} diff --git a/src/tools/clippy/tests/ui/floating_point_powf.stderr b/src/tools/clippy/tests/ui/floating_point_powf.stderr new file mode 100644 index 0000000000000..ad5163f0079be --- /dev/null +++ b/src/tools/clippy/tests/ui/floating_point_powf.stderr @@ -0,0 +1,150 @@ +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:6:13 + | +LL | let _ = 2f32.powf(x); + | ^^^^^^^^^^^^ help: consider using: `x.exp2()` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:7:13 + | +LL | let _ = 2f32.powf(3.1); + | ^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:8:13 + | +LL | let _ = 2f32.powf(-3.1); + | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:9:13 + | +LL | let _ = std::f32::consts::E.powf(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:10:13 + | +LL | let _ = std::f32::consts::E.powf(3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:11:13 + | +LL | let _ = std::f32::consts::E.powf(-3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp()` + +error: square-root of a number can be computed more efficiently and accurately + --> $DIR/floating_point_powf.rs:12:13 + | +LL | let _ = x.powf(1.0 / 2.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()` + +error: cube-root of a number can be computed more accurately + --> $DIR/floating_point_powf.rs:13:13 + | +LL | let _ = x.powf(1.0 / 3.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()` + | + = note: `-D clippy::imprecise-flops` implied by `-D warnings` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:14:13 + | +LL | let _ = x.powf(2.0); + | ^^^^^^^^^^^ help: consider using: `x.powi(2)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:15:13 + | +LL | let _ = x.powf(-2.0); + | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:16:13 + | +LL | let _ = x.powf(16_777_215.0); + | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(16_777_215)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:17:13 + | +LL | let _ = x.powf(-16_777_215.0); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-16_777_215)` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:25:13 + | +LL | let _ = 2f64.powf(x); + | ^^^^^^^^^^^^ help: consider using: `x.exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:26:13 + | +LL | let _ = 2f64.powf(3.1); + | ^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:27:13 + | +LL | let _ = 2f64.powf(-3.1); + | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp2()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:28:13 + | +LL | let _ = std::f64::consts::E.powf(x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:29:13 + | +LL | let _ = std::f64::consts::E.powf(3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp()` + +error: exponent for bases 2 and e can be computed more accurately + --> $DIR/floating_point_powf.rs:30:13 + | +LL | let _ = std::f64::consts::E.powf(-3.1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp()` + +error: square-root of a number can be computed more efficiently and accurately + --> $DIR/floating_point_powf.rs:31:13 + | +LL | let _ = x.powf(1.0 / 2.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()` + +error: cube-root of a number can be computed more accurately + --> $DIR/floating_point_powf.rs:32:13 + | +LL | let _ = x.powf(1.0 / 3.0); + | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:33:13 + | +LL | let _ = x.powf(2.0); + | ^^^^^^^^^^^ help: consider using: `x.powi(2)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:34:13 + | +LL | let _ = x.powf(-2.0); + | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:35:13 + | +LL | let _ = x.powf(-2_147_483_648.0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-2_147_483_648)` + +error: exponentiation with integer powers can be computed more efficiently + --> $DIR/floating_point_powf.rs:36:13 + | +LL | let _ = x.powf(2_147_483_647.0); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2_147_483_647)` + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_address_comparisons.rs b/src/tools/clippy/tests/ui/fn_address_comparisons.rs new file mode 100644 index 0000000000000..362dcb4fd80ca --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_address_comparisons.rs @@ -0,0 +1,20 @@ +use std::fmt::Debug; +use std::ptr; +use std::rc::Rc; +use std::sync::Arc; + +fn a() {} + +#[warn(clippy::fn_address_comparisons)] +fn main() { + type F = fn(); + let f: F = a; + let g: F = f; + + // These should fail: + let _ = f == a; + let _ = f != a; + + // These should be fine: + let _ = f == g; +} diff --git a/src/tools/clippy/tests/ui/fn_address_comparisons.stderr b/src/tools/clippy/tests/ui/fn_address_comparisons.stderr new file mode 100644 index 0000000000000..9c1b5419a4319 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_address_comparisons.stderr @@ -0,0 +1,16 @@ +error: comparing with a non-unique address of a function item + --> $DIR/fn_address_comparisons.rs:15:13 + | +LL | let _ = f == a; + | ^^^^^^ + | + = note: `-D clippy::fn-address-comparisons` implied by `-D warnings` + +error: comparing with a non-unique address of a function item + --> $DIR/fn_address_comparisons.rs:16:13 + | +LL | let _ = f != a; + | ^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_params_excessive_bools.rs b/src/tools/clippy/tests/ui/fn_params_excessive_bools.rs new file mode 100644 index 0000000000000..7d6fd607e6545 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_params_excessive_bools.rs @@ -0,0 +1,44 @@ +#![warn(clippy::fn_params_excessive_bools)] + +extern "C" { + fn f(_: bool, _: bool, _: bool, _: bool); +} + +macro_rules! foo { + () => { + fn fff(_: bool, _: bool, _: bool, _: bool) {} + }; +} + +foo!(); + +#[no_mangle] +extern "C" fn k(_: bool, _: bool, _: bool, _: bool) {} +fn g(_: bool, _: bool, _: bool, _: bool) {} +fn h(_: bool, _: bool, _: bool) {} +fn e(_: S, _: S, _: Box, _: Vec) {} +fn t(_: S, _: S, _: Box, _: Vec, _: bool, _: bool, _: bool, _: bool) {} + +struct S {} +trait Trait { + fn f(_: bool, _: bool, _: bool, _: bool); + fn g(_: bool, _: bool, _: bool, _: Vec); +} + +impl S { + fn f(&self, _: bool, _: bool, _: bool, _: bool) {} + fn g(&self, _: bool, _: bool, _: bool) {} + #[no_mangle] + extern "C" fn h(_: bool, _: bool, _: bool, _: bool) {} +} + +impl Trait for S { + fn f(_: bool, _: bool, _: bool, _: bool) {} + fn g(_: bool, _: bool, _: bool, _: Vec) {} +} + +fn main() { + fn n(_: bool, _: u32, _: bool, _: Box, _: bool, _: bool) { + fn nn(_: bool, _: bool, _: bool, _: bool) {} + } +} diff --git a/src/tools/clippy/tests/ui/fn_params_excessive_bools.stderr b/src/tools/clippy/tests/ui/fn_params_excessive_bools.stderr new file mode 100644 index 0000000000000..4e5dbc261d66b --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_params_excessive_bools.stderr @@ -0,0 +1,53 @@ +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:17:1 + | +LL | fn g(_: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::fn-params-excessive-bools` implied by `-D warnings` + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:20:1 + | +LL | fn t(_: S, _: S, _: Box, _: Vec, _: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:24:5 + | +LL | fn f(_: bool, _: bool, _: bool, _: bool); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:29:5 + | +LL | fn f(&self, _: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:41:5 + | +LL | / fn n(_: bool, _: u32, _: bool, _: Box, _: bool, _: bool) { +LL | | fn nn(_: bool, _: bool, _: bool, _: bool) {} +LL | | } + | |_____^ + | + = help: consider refactoring bools into two-variant enums + +error: more than 3 bools in function parameters + --> $DIR/fn_params_excessive_bools.rs:42:9 + | +LL | fn nn(_: bool, _: bool, _: bool, _: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider refactoring bools into two-variant enums + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast.rs b/src/tools/clippy/tests/ui/fn_to_numeric_cast.rs new file mode 100644 index 0000000000000..a456c085c8761 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast.rs @@ -0,0 +1,55 @@ +// ignore-32bit + +#![warn(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)] + +fn foo() -> String { + String::new() +} + +fn test_function_to_numeric_cast() { + let _ = foo as i8; + let _ = foo as i16; + let _ = foo as i32; + let _ = foo as i64; + let _ = foo as i128; + let _ = foo as isize; + + let _ = foo as u8; + let _ = foo as u16; + let _ = foo as u32; + let _ = foo as u64; + let _ = foo as u128; + + // Casting to usize is OK and should not warn + let _ = foo as usize; + + // Cast `f` (a `FnDef`) to `fn()` should not warn + fn f() {} + let _ = f as fn(); +} + +fn test_function_var_to_numeric_cast() { + let abc: fn() -> String = foo; + + let _ = abc as i8; + let _ = abc as i16; + let _ = abc as i32; + let _ = abc as i64; + let _ = abc as i128; + let _ = abc as isize; + + let _ = abc as u8; + let _ = abc as u16; + let _ = abc as u32; + let _ = abc as u64; + let _ = abc as u128; + + // Casting to usize is OK and should not warn + let _ = abc as usize; +} + +fn fn_with_fn_args(f: fn(i32) -> i32) -> i32 { + f as i32 +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast.stderr b/src/tools/clippy/tests/ui/fn_to_numeric_cast.stderr new file mode 100644 index 0000000000000..e9549e157cd91 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast.stderr @@ -0,0 +1,144 @@ +error: casting function pointer `foo` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:10:13 + | +LL | let _ = foo as i8; + | ^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast-with-truncation` implied by `-D warnings` + +error: casting function pointer `foo` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:11:13 + | +LL | let _ = foo as i16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:12:13 + | +LL | let _ = foo as i32; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i64` + --> $DIR/fn_to_numeric_cast.rs:13:13 + | +LL | let _ = foo as i64; + | ^^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast` implied by `-D warnings` + +error: casting function pointer `foo` to `i128` + --> $DIR/fn_to_numeric_cast.rs:14:13 + | +LL | let _ = foo as i128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `isize` + --> $DIR/fn_to_numeric_cast.rs:15:13 + | +LL | let _ = foo as isize; + | ^^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:17:13 + | +LL | let _ = foo as u8; + | ^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:18:13 + | +LL | let _ = foo as u16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:19:13 + | +LL | let _ = foo as u32; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u64` + --> $DIR/fn_to_numeric_cast.rs:20:13 + | +LL | let _ = foo as u64; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u128` + --> $DIR/fn_to_numeric_cast.rs:21:13 + | +LL | let _ = foo as u128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `abc` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:34:13 + | +LL | let _ = abc as i8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:35:13 + | +LL | let _ = abc as i16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:36:13 + | +LL | let _ = abc as i32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i64` + --> $DIR/fn_to_numeric_cast.rs:37:13 + | +LL | let _ = abc as i64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i128` + --> $DIR/fn_to_numeric_cast.rs:38:13 + | +LL | let _ = abc as i128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `isize` + --> $DIR/fn_to_numeric_cast.rs:39:13 + | +LL | let _ = abc as isize; + | ^^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:41:13 + | +LL | let _ = abc as u8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:42:13 + | +LL | let _ = abc as u16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:43:13 + | +LL | let _ = abc as u32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u64` + --> $DIR/fn_to_numeric_cast.rs:44:13 + | +LL | let _ = abc as u64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u128` + --> $DIR/fn_to_numeric_cast.rs:45:13 + | +LL | let _ = abc as u128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `f` to `i32`, which truncates the value + --> $DIR/fn_to_numeric_cast.rs:52:5 + | +LL | f as i32 + | ^^^^^^^^ help: try: `f as usize` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.rs b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.rs new file mode 100644 index 0000000000000..04ee985c0863d --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.rs @@ -0,0 +1,55 @@ +// ignore-64bit + +#![warn(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)] + +fn foo() -> String { + String::new() +} + +fn test_function_to_numeric_cast() { + let _ = foo as i8; + let _ = foo as i16; + let _ = foo as i32; + let _ = foo as i64; + let _ = foo as i128; + let _ = foo as isize; + + let _ = foo as u8; + let _ = foo as u16; + let _ = foo as u32; + let _ = foo as u64; + let _ = foo as u128; + + // Casting to usize is OK and should not warn + let _ = foo as usize; + + // Cast `f` (a `FnDef`) to `fn()` should not warn + fn f() {} + let _ = f as fn(); +} + +fn test_function_var_to_numeric_cast() { + let abc: fn() -> String = foo; + + let _ = abc as i8; + let _ = abc as i16; + let _ = abc as i32; + let _ = abc as i64; + let _ = abc as i128; + let _ = abc as isize; + + let _ = abc as u8; + let _ = abc as u16; + let _ = abc as u32; + let _ = abc as u64; + let _ = abc as u128; + + // Casting to usize is OK and should not warn + let _ = abc as usize; +} + +fn fn_with_fn_args(f: fn(i32) -> i32) -> i32 { + f as i32 +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.stderr b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.stderr new file mode 100644 index 0000000000000..08dd611d67524 --- /dev/null +++ b/src/tools/clippy/tests/ui/fn_to_numeric_cast_32bit.stderr @@ -0,0 +1,144 @@ +error: casting function pointer `foo` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:10:13 + | +LL | let _ = foo as i8; + | ^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast-with-truncation` implied by `-D warnings` + +error: casting function pointer `foo` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:11:13 + | +LL | let _ = foo as i16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i32` + --> $DIR/fn_to_numeric_cast_32bit.rs:12:13 + | +LL | let _ = foo as i32; + | ^^^^^^^^^^ help: try: `foo as usize` + | + = note: `-D clippy::fn-to-numeric-cast` implied by `-D warnings` + +error: casting function pointer `foo` to `i64` + --> $DIR/fn_to_numeric_cast_32bit.rs:13:13 + | +LL | let _ = foo as i64; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `i128` + --> $DIR/fn_to_numeric_cast_32bit.rs:14:13 + | +LL | let _ = foo as i128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `isize` + --> $DIR/fn_to_numeric_cast_32bit.rs:15:13 + | +LL | let _ = foo as isize; + | ^^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:17:13 + | +LL | let _ = foo as u8; + | ^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:18:13 + | +LL | let _ = foo as u16; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u32` + --> $DIR/fn_to_numeric_cast_32bit.rs:19:13 + | +LL | let _ = foo as u32; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u64` + --> $DIR/fn_to_numeric_cast_32bit.rs:20:13 + | +LL | let _ = foo as u64; + | ^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `foo` to `u128` + --> $DIR/fn_to_numeric_cast_32bit.rs:21:13 + | +LL | let _ = foo as u128; + | ^^^^^^^^^^^ help: try: `foo as usize` + +error: casting function pointer `abc` to `i8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:34:13 + | +LL | let _ = abc as i8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:35:13 + | +LL | let _ = abc as i16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i32` + --> $DIR/fn_to_numeric_cast_32bit.rs:36:13 + | +LL | let _ = abc as i32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i64` + --> $DIR/fn_to_numeric_cast_32bit.rs:37:13 + | +LL | let _ = abc as i64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `i128` + --> $DIR/fn_to_numeric_cast_32bit.rs:38:13 + | +LL | let _ = abc as i128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `isize` + --> $DIR/fn_to_numeric_cast_32bit.rs:39:13 + | +LL | let _ = abc as isize; + | ^^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u8`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:41:13 + | +LL | let _ = abc as u8; + | ^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u16`, which truncates the value + --> $DIR/fn_to_numeric_cast_32bit.rs:42:13 + | +LL | let _ = abc as u16; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u32` + --> $DIR/fn_to_numeric_cast_32bit.rs:43:13 + | +LL | let _ = abc as u32; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u64` + --> $DIR/fn_to_numeric_cast_32bit.rs:44:13 + | +LL | let _ = abc as u64; + | ^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `abc` to `u128` + --> $DIR/fn_to_numeric_cast_32bit.rs:45:13 + | +LL | let _ = abc as u128; + | ^^^^^^^^^^^ help: try: `abc as usize` + +error: casting function pointer `f` to `i32` + --> $DIR/fn_to_numeric_cast_32bit.rs:52:5 + | +LL | f as i32 + | ^^^^^^^^ help: try: `f as usize` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/for_kv_map.rs b/src/tools/clippy/tests/ui/for_kv_map.rs new file mode 100644 index 0000000000000..39a8d960a7e91 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_kv_map.rs @@ -0,0 +1,50 @@ +#![warn(clippy::for_kv_map)] +#![allow(clippy::used_underscore_binding)] + +use std::collections::*; +use std::rc::Rc; + +fn main() { + let m: HashMap = HashMap::new(); + for (_, v) in &m { + let _v = v; + } + + let m: Rc> = Rc::new(HashMap::new()); + for (_, v) in &*m { + let _v = v; + // Here the `*` is not actually necessary, but the test tests that we don't + // suggest + // `in *m.values()` as we used to + } + + let mut m: HashMap = HashMap::new(); + for (_, v) in &mut m { + let _v = v; + } + + let m: &mut HashMap = &mut HashMap::new(); + for (_, v) in &mut *m { + let _v = v; + } + + let m: HashMap = HashMap::new(); + let rm = &m; + for (k, _value) in rm { + let _k = k; + } + + // The following should not produce warnings. + + let m: HashMap = HashMap::new(); + // No error, _value is actually used + for (k, _value) in &m { + let _ = _value; + let _k = k; + } + + let m: HashMap = Default::default(); + for (_, v) in m { + let _v = v; + } +} diff --git a/src/tools/clippy/tests/ui/for_kv_map.stderr b/src/tools/clippy/tests/ui/for_kv_map.stderr new file mode 100644 index 0000000000000..241758c542cfb --- /dev/null +++ b/src/tools/clippy/tests/ui/for_kv_map.stderr @@ -0,0 +1,58 @@ +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:9:19 + | +LL | for (_, v) in &m { + | ^^ + | + = note: `-D clippy::for-kv-map` implied by `-D warnings` +help: use the corresponding method + | +LL | for v in m.values() { + | ^ ^^^^^^^^^^ + +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:14:19 + | +LL | for (_, v) in &*m { + | ^^^ + | +help: use the corresponding method + | +LL | for v in (*m).values() { + | ^ ^^^^^^^^^^^^^ + +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:22:19 + | +LL | for (_, v) in &mut m { + | ^^^^^^ + | +help: use the corresponding method + | +LL | for v in m.values_mut() { + | ^ ^^^^^^^^^^^^^^ + +error: you seem to want to iterate on a map's values + --> $DIR/for_kv_map.rs:27:19 + | +LL | for (_, v) in &mut *m { + | ^^^^^^^ + | +help: use the corresponding method + | +LL | for v in (*m).values_mut() { + | ^ ^^^^^^^^^^^^^^^^^ + +error: you seem to want to iterate on a map's keys + --> $DIR/for_kv_map.rs:33:24 + | +LL | for (k, _value) in rm { + | ^^ + | +help: use the corresponding method + | +LL | for k in rm.keys() { + | ^ ^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/for_loop_fixable.fixed b/src/tools/clippy/tests/ui/for_loop_fixable.fixed new file mode 100644 index 0000000000000..5fc84ada9efdd --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_fixable.fixed @@ -0,0 +1,337 @@ +// run-rustfix + +#![allow(dead_code, unused)] + +use std::collections::*; + +#[warn(clippy::all)] +struct Unrelated(Vec); +impl Unrelated { + fn next(&self) -> std::slice::Iter { + self.0.iter() + } + + fn iter(&self) -> std::slice::Iter { + self.0.iter() + } +} + +#[warn( + clippy::needless_range_loop, + clippy::explicit_iter_loop, + clippy::explicit_into_iter_loop, + clippy::iter_next_loop, + clippy::reverse_range_loop, + clippy::for_kv_map +)] +#[allow( + clippy::linkedlist, + clippy::shadow_unrelated, + clippy::unnecessary_mut_passed, + clippy::similar_names +)] +#[allow(clippy::many_single_char_names, unused_variables)] +fn main() { + const MAX_LEN: usize = 42; + let mut vec = vec![1, 2, 3, 4]; + + for i in (0..10).rev() { + println!("{}", i); + } + + for i in (0..=10).rev() { + println!("{}", i); + } + + for i in (0..MAX_LEN).rev() { + println!("{}", i); + } + + for i in 5..=5 { + // not an error, this is the range with only one element “5” + println!("{}", i); + } + + for i in 0..10 { + // not an error, the start index is less than the end index + println!("{}", i); + } + + for i in -10..0 { + // not an error + println!("{}", i); + } + + for i in (10..0).map(|x| x * 2) { + // not an error, it can't be known what arbitrary methods do to a range + println!("{}", i); + } + + // testing that the empty range lint folds constants + for i in (5 + 4..10).rev() { + println!("{}", i); + } + + for i in ((3 - 1)..(5 + 2)).rev() { + println!("{}", i); + } + + for i in (2 * 2)..(2 * 3) { + // no error, 4..6 is fine + println!("{}", i); + } + + let x = 42; + for i in x..10 { + // no error, not constant-foldable + println!("{}", i); + } + + // See #601 + for i in 0..10 { + // no error, id_col does not exist outside the loop + let mut id_col = vec![0f64; 10]; + id_col[i] = 1f64; + } + + for _v in &vec {} + + for _v in &mut vec {} + + let out_vec = vec![1, 2, 3]; + for _v in out_vec {} + + for _v in &vec {} // these are fine + for _v in &mut vec {} // these are fine + + for _v in &[1, 2, 3] {} + + for _v in (&mut [1, 2, 3]).iter() {} // no error + + for _v in &[0; 32] {} + + for _v in [0; 33].iter() {} // no error + + let ll: LinkedList<()> = LinkedList::new(); + for _v in &ll {} + + let vd: VecDeque<()> = VecDeque::new(); + for _v in &vd {} + + let bh: BinaryHeap<()> = BinaryHeap::new(); + for _v in &bh {} + + let hm: HashMap<(), ()> = HashMap::new(); + for _v in &hm {} + + let bt: BTreeMap<(), ()> = BTreeMap::new(); + for _v in &bt {} + + let hs: HashSet<()> = HashSet::new(); + for _v in &hs {} + + let bs: BTreeSet<()> = BTreeSet::new(); + for _v in &bs {} + + let u = Unrelated(vec![]); + for _v in u.next() {} // no error + for _v in u.iter() {} // no error + + let mut out = vec![]; + vec.iter().cloned().map(|x| out.push(x)).collect::>(); + let _y = vec.iter().cloned().map(|x| out.push(x)).collect::>(); // this is fine + + // Loop with explicit counter variable + + // Potential false positives + let mut _index = 0; + _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + _index += 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + if true { + _index = 1 + } + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + let mut _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index *= 2; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index = 1; + _index += 1 + } + + let mut _index = 0; + + for _v in &vec { + let mut _index = 0; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index = 0; + } + + let mut _index = 0; + for _v in &vec { + for _x in 0..1 { + _index += 1; + } + _index += 1 + } + + let mut _index = 0; + for x in &vec { + if *x == 1 { + _index += 1 + } + } + + let mut _index = 0; + if true { + _index = 1 + }; + for _v in &vec { + _index += 1 + } + + let mut _index = 1; + if false { + _index = 0 + }; + for _v in &vec { + _index += 1 + } + + let mut index = 0; + { + let mut _x = &mut index; + } + for _v in &vec { + _index += 1 + } + + let mut index = 0; + for _v in &vec { + index += 1 + } + println!("index: {}", index); + + fn f(_: &T, _: &T) -> bool { + unimplemented!() + } + fn g(_: &mut [T], _: usize, _: usize) { + unimplemented!() + } + for i in 1..vec.len() { + if f(&vec[i - 1], &vec[i]) { + g(&mut vec, i - 1, i); + } + } + + for mid in 1..vec.len() { + let (_, _) = vec.split_at(mid); + } +} + +fn partition(v: &mut [T]) -> usize { + let pivot = v.len() - 1; + let mut i = 0; + for j in 0..pivot { + if v[j] <= v[pivot] { + v.swap(i, j); + i += 1; + } + } + v.swap(i, pivot); + i +} + +#[warn(clippy::needless_range_loop)] +pub fn manual_copy_same_destination(dst: &mut [i32], d: usize, s: usize) { + // Same source and destination - don't trigger lint + for i in 0..dst.len() { + dst[d + i] = dst[s + i]; + } +} + +mod issue_2496 { + pub trait Handle { + fn new_for_index(index: usize) -> Self; + fn index(&self) -> usize; + } + + pub fn test() -> H { + for x in 0..5 { + let next_handle = H::new_for_index(x); + println!("{}", next_handle.index()); + } + unimplemented!() + } +} + +// explicit_into_iter_loop bad suggestions +#[warn(clippy::explicit_into_iter_loop, clippy::explicit_iter_loop)] +mod issue_4958 { + fn takes_iterator(iterator: &T) + where + for<'a> &'a T: IntoIterator, + { + for i in iterator { + println!("{}", i); + } + } + + struct T; + impl IntoIterator for &T { + type Item = (); + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + vec![].into_iter() + } + } + + fn more_tests() { + let t = T; + let r = &t; + let rr = &&t; + + // This case is handled by `explicit_iter_loop`. No idea why. + for _ in &t {} + + for _ in r {} + + // No suggestion for this. + // We'd have to suggest `for _ in *rr {}` which is less clear. + for _ in rr.into_iter() {} + } +} diff --git a/src/tools/clippy/tests/ui/for_loop_fixable.rs b/src/tools/clippy/tests/ui/for_loop_fixable.rs new file mode 100644 index 0000000000000..4165b0dc00494 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_fixable.rs @@ -0,0 +1,337 @@ +// run-rustfix + +#![allow(dead_code, unused)] + +use std::collections::*; + +#[warn(clippy::all)] +struct Unrelated(Vec); +impl Unrelated { + fn next(&self) -> std::slice::Iter { + self.0.iter() + } + + fn iter(&self) -> std::slice::Iter { + self.0.iter() + } +} + +#[warn( + clippy::needless_range_loop, + clippy::explicit_iter_loop, + clippy::explicit_into_iter_loop, + clippy::iter_next_loop, + clippy::reverse_range_loop, + clippy::for_kv_map +)] +#[allow( + clippy::linkedlist, + clippy::shadow_unrelated, + clippy::unnecessary_mut_passed, + clippy::similar_names +)] +#[allow(clippy::many_single_char_names, unused_variables)] +fn main() { + const MAX_LEN: usize = 42; + let mut vec = vec![1, 2, 3, 4]; + + for i in 10..0 { + println!("{}", i); + } + + for i in 10..=0 { + println!("{}", i); + } + + for i in MAX_LEN..0 { + println!("{}", i); + } + + for i in 5..=5 { + // not an error, this is the range with only one element “5” + println!("{}", i); + } + + for i in 0..10 { + // not an error, the start index is less than the end index + println!("{}", i); + } + + for i in -10..0 { + // not an error + println!("{}", i); + } + + for i in (10..0).map(|x| x * 2) { + // not an error, it can't be known what arbitrary methods do to a range + println!("{}", i); + } + + // testing that the empty range lint folds constants + for i in 10..5 + 4 { + println!("{}", i); + } + + for i in (5 + 2)..(3 - 1) { + println!("{}", i); + } + + for i in (2 * 2)..(2 * 3) { + // no error, 4..6 is fine + println!("{}", i); + } + + let x = 42; + for i in x..10 { + // no error, not constant-foldable + println!("{}", i); + } + + // See #601 + for i in 0..10 { + // no error, id_col does not exist outside the loop + let mut id_col = vec![0f64; 10]; + id_col[i] = 1f64; + } + + for _v in vec.iter() {} + + for _v in vec.iter_mut() {} + + let out_vec = vec![1, 2, 3]; + for _v in out_vec.into_iter() {} + + for _v in &vec {} // these are fine + for _v in &mut vec {} // these are fine + + for _v in [1, 2, 3].iter() {} + + for _v in (&mut [1, 2, 3]).iter() {} // no error + + for _v in [0; 32].iter() {} + + for _v in [0; 33].iter() {} // no error + + let ll: LinkedList<()> = LinkedList::new(); + for _v in ll.iter() {} + + let vd: VecDeque<()> = VecDeque::new(); + for _v in vd.iter() {} + + let bh: BinaryHeap<()> = BinaryHeap::new(); + for _v in bh.iter() {} + + let hm: HashMap<(), ()> = HashMap::new(); + for _v in hm.iter() {} + + let bt: BTreeMap<(), ()> = BTreeMap::new(); + for _v in bt.iter() {} + + let hs: HashSet<()> = HashSet::new(); + for _v in hs.iter() {} + + let bs: BTreeSet<()> = BTreeSet::new(); + for _v in bs.iter() {} + + let u = Unrelated(vec![]); + for _v in u.next() {} // no error + for _v in u.iter() {} // no error + + let mut out = vec![]; + vec.iter().cloned().map(|x| out.push(x)).collect::>(); + let _y = vec.iter().cloned().map(|x| out.push(x)).collect::>(); // this is fine + + // Loop with explicit counter variable + + // Potential false positives + let mut _index = 0; + _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + _index += 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + if true { + _index = 1 + } + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + let mut _index = 1; + for _v in &vec { + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index *= 2; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index = 1; + _index += 1 + } + + let mut _index = 0; + + for _v in &vec { + let mut _index = 0; + _index += 1 + } + + let mut _index = 0; + for _v in &vec { + _index += 1; + _index = 0; + } + + let mut _index = 0; + for _v in &vec { + for _x in 0..1 { + _index += 1; + } + _index += 1 + } + + let mut _index = 0; + for x in &vec { + if *x == 1 { + _index += 1 + } + } + + let mut _index = 0; + if true { + _index = 1 + }; + for _v in &vec { + _index += 1 + } + + let mut _index = 1; + if false { + _index = 0 + }; + for _v in &vec { + _index += 1 + } + + let mut index = 0; + { + let mut _x = &mut index; + } + for _v in &vec { + _index += 1 + } + + let mut index = 0; + for _v in &vec { + index += 1 + } + println!("index: {}", index); + + fn f(_: &T, _: &T) -> bool { + unimplemented!() + } + fn g(_: &mut [T], _: usize, _: usize) { + unimplemented!() + } + for i in 1..vec.len() { + if f(&vec[i - 1], &vec[i]) { + g(&mut vec, i - 1, i); + } + } + + for mid in 1..vec.len() { + let (_, _) = vec.split_at(mid); + } +} + +fn partition(v: &mut [T]) -> usize { + let pivot = v.len() - 1; + let mut i = 0; + for j in 0..pivot { + if v[j] <= v[pivot] { + v.swap(i, j); + i += 1; + } + } + v.swap(i, pivot); + i +} + +#[warn(clippy::needless_range_loop)] +pub fn manual_copy_same_destination(dst: &mut [i32], d: usize, s: usize) { + // Same source and destination - don't trigger lint + for i in 0..dst.len() { + dst[d + i] = dst[s + i]; + } +} + +mod issue_2496 { + pub trait Handle { + fn new_for_index(index: usize) -> Self; + fn index(&self) -> usize; + } + + pub fn test() -> H { + for x in 0..5 { + let next_handle = H::new_for_index(x); + println!("{}", next_handle.index()); + } + unimplemented!() + } +} + +// explicit_into_iter_loop bad suggestions +#[warn(clippy::explicit_into_iter_loop, clippy::explicit_iter_loop)] +mod issue_4958 { + fn takes_iterator(iterator: &T) + where + for<'a> &'a T: IntoIterator, + { + for i in iterator.into_iter() { + println!("{}", i); + } + } + + struct T; + impl IntoIterator for &T { + type Item = (); + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + vec![].into_iter() + } + } + + fn more_tests() { + let t = T; + let r = &t; + let rr = &&t; + + // This case is handled by `explicit_iter_loop`. No idea why. + for _ in t.into_iter() {} + + for _ in r.into_iter() {} + + // No suggestion for this. + // We'd have to suggest `for _ in *rr {}` which is less clear. + for _ in rr.into_iter() {} + } +} diff --git a/src/tools/clippy/tests/ui/for_loop_fixable.stderr b/src/tools/clippy/tests/ui/for_loop_fixable.stderr new file mode 100644 index 0000000000000..cffb4b9f0a9c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_fixable.stderr @@ -0,0 +1,152 @@ +error: this range is empty so this for loop will never run + --> $DIR/for_loop_fixable.rs:38:14 + | +LL | for i in 10..0 { + | ^^^^^ + | + = note: `-D clippy::reverse-range-loop` implied by `-D warnings` +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..10).rev() { + | ^^^^^^^^^^^^^ + +error: this range is empty so this for loop will never run + --> $DIR/for_loop_fixable.rs:42:14 + | +LL | for i in 10..=0 { + | ^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..=10).rev() { + | ^^^^^^^^^^^^^^ + +error: this range is empty so this for loop will never run + --> $DIR/for_loop_fixable.rs:46:14 + | +LL | for i in MAX_LEN..0 { + | ^^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (0..MAX_LEN).rev() { + | ^^^^^^^^^^^^^^^^^^ + +error: this range is empty so this for loop will never run + --> $DIR/for_loop_fixable.rs:71:14 + | +LL | for i in 10..5 + 4 { + | ^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in (5 + 4..10).rev() { + | ^^^^^^^^^^^^^^^^^ + +error: this range is empty so this for loop will never run + --> $DIR/for_loop_fixable.rs:75:14 + | +LL | for i in (5 + 2)..(3 - 1) { + | ^^^^^^^^^^^^^^^^ + | +help: consider using the following if you are attempting to iterate over this range in reverse + | +LL | for i in ((3 - 1)..(5 + 2)).rev() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:97:15 + | +LL | for _v in vec.iter() {} + | ^^^^^^^^^^ help: to write this more concisely, try: `&vec` + | + = note: `-D clippy::explicit-iter-loop` implied by `-D warnings` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:99:15 + | +LL | for _v in vec.iter_mut() {} + | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut vec` + +error: it is more concise to loop over containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:102:15 + | +LL | for _v in out_vec.into_iter() {} + | ^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `out_vec` + | + = note: `-D clippy::explicit-into-iter-loop` implied by `-D warnings` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:107:15 + | +LL | for _v in [1, 2, 3].iter() {} + | ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[1, 2, 3]` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:111:15 + | +LL | for _v in [0; 32].iter() {} + | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 32]` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:116:15 + | +LL | for _v in ll.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&ll` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:119:15 + | +LL | for _v in vd.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&vd` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:122:15 + | +LL | for _v in bh.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&bh` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:125:15 + | +LL | for _v in hm.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&hm` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:128:15 + | +LL | for _v in bt.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&bt` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:131:15 + | +LL | for _v in hs.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&hs` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:134:15 + | +LL | for _v in bs.iter() {} + | ^^^^^^^^^ help: to write this more concisely, try: `&bs` + +error: it is more concise to loop over containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:309:18 + | +LL | for i in iterator.into_iter() { + | ^^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `iterator` + +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:329:18 + | +LL | for _ in t.into_iter() {} + | ^^^^^^^^^^^^^ help: to write this more concisely, try: `&t` + +error: it is more concise to loop over containers instead of using explicit iteration methods + --> $DIR/for_loop_fixable.rs:331:18 + | +LL | for _ in r.into_iter() {} + | ^^^^^^^^^^^^^ help: to write this more concisely, try: `r` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/for_loop_over_option_result.rs b/src/tools/clippy/tests/ui/for_loop_over_option_result.rs new file mode 100644 index 0000000000000..6b207b26b6b79 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_over_option_result.rs @@ -0,0 +1,59 @@ +#![warn(clippy::for_loop_over_option, clippy::for_loop_over_result)] + +/// Tests for_loop_over_result and for_loop_over_option + +fn for_loop_over_option_and_result() { + let option = Some(1); + let result = option.ok_or("x not found"); + let v = vec![0, 1, 2]; + + // check FOR_LOOP_OVER_OPTION lint + for x in option { + println!("{}", x); + } + + // check FOR_LOOP_OVER_RESULT lint + for x in result { + println!("{}", x); + } + + for x in option.ok_or("x not found") { + println!("{}", x); + } + + // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call + // in the chain + for x in v.iter().next() { + println!("{}", x); + } + + // make sure we lint when next() is not the last call in the chain + for x in v.iter().next().and(Some(0)) { + println!("{}", x); + } + + for x in v.iter().next().ok_or("x not found") { + println!("{}", x); + } + + // check for false positives + + // for loop false positive + for x in v { + println!("{}", x); + } + + // while let false positive for Option + while let Some(x) = option { + println!("{}", x); + break; + } + + // while let false positive for Result + while let Ok(x) = result { + println!("{}", x); + break; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/for_loop_over_option_result.stderr b/src/tools/clippy/tests/ui/for_loop_over_option_result.stderr new file mode 100644 index 0000000000000..194a0bfec5b32 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_over_option_result.stderr @@ -0,0 +1,72 @@ +error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_option_result.rs:11:14 + | +LL | for x in option { + | ^^^^^^ + | + = note: `-D clippy::for-loop-over-option` implied by `-D warnings` + = help: consider replacing `for x in option` with `if let Some(x) = option` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_option_result.rs:16:14 + | +LL | for x in result { + | ^^^^^^ + | + = note: `-D clippy::for-loop-over-result` implied by `-D warnings` + = help: consider replacing `for x in result` with `if let Ok(x) = result` + +error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_option_result.rs:20:14 + | +LL | for x in option.ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` + +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want + --> $DIR/for_loop_over_option_result.rs:26:14 + | +LL | for x in v.iter().next() { + | ^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::iter_next_loop)]` on by default + +error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_option_result.rs:31:14 + | +LL | for x in v.iter().next().and(Some(0)) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` + +error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement. + --> $DIR/for_loop_over_option_result.rs:35:14 + | +LL | for x in v.iter().next().ok_or("x not found") { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` + +error: this loop never actually loops + --> $DIR/for_loop_over_option_result.rs:47:5 + | +LL | / while let Some(x) = option { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + | + = note: `#[deny(clippy::never_loop)]` on by default + +error: this loop never actually loops + --> $DIR/for_loop_over_option_result.rs:53:5 + | +LL | / while let Ok(x) = result { +LL | | println!("{}", x); +LL | | break; +LL | | } + | |_____^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/for_loop_unfixable.rs b/src/tools/clippy/tests/ui/for_loop_unfixable.rs new file mode 100644 index 0000000000000..179b255e08ca7 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_unfixable.rs @@ -0,0 +1,40 @@ +// Tests from for_loop.rs that don't have suggestions + +#[warn( + clippy::needless_range_loop, + clippy::explicit_iter_loop, + clippy::explicit_into_iter_loop, + clippy::iter_next_loop, + clippy::reverse_range_loop, + clippy::for_kv_map +)] +#[allow( + clippy::linkedlist, + clippy::shadow_unrelated, + clippy::unnecessary_mut_passed, + clippy::similar_names, + unused, + dead_code +)] +#[allow(clippy::many_single_char_names, unused_variables)] +fn main() { + for i in 5..5 { + println!("{}", i); + } + + let vec = vec![1, 2, 3, 4]; + + for _v in vec.iter().next() {} + + for i in (5 + 2)..(8 - 1) { + println!("{}", i); + } + + const ZERO: usize = 0; + + for i in ZERO..vec.len() { + if f(&vec[i], &vec[i]) { + panic!("at the disco"); + } + } +} diff --git a/src/tools/clippy/tests/ui/for_loop_unfixable.stderr b/src/tools/clippy/tests/ui/for_loop_unfixable.stderr new file mode 100644 index 0000000000000..1da8e0f3588d7 --- /dev/null +++ b/src/tools/clippy/tests/ui/for_loop_unfixable.stderr @@ -0,0 +1,9 @@ +error[E0425]: cannot find function `f` in this scope + --> $DIR/for_loop_unfixable.rs:36:12 + | +LL | if f(&vec[i], &vec[i]) { + | ^ help: a local variable with a similar name exists: `i` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/src/tools/clippy/tests/ui/forget_ref.rs b/src/tools/clippy/tests/ui/forget_ref.rs new file mode 100644 index 0000000000000..447fdbe1fac53 --- /dev/null +++ b/src/tools/clippy/tests/ui/forget_ref.rs @@ -0,0 +1,48 @@ +#![warn(clippy::forget_ref)] +#![allow(clippy::toplevel_ref_arg)] + +use std::mem::forget; + +struct SomeStruct; + +fn main() { + forget(&SomeStruct); + + let mut owned = SomeStruct; + forget(&owned); + forget(&&owned); + forget(&mut owned); + forget(owned); //OK + + let reference1 = &SomeStruct; + forget(&*reference1); + + let reference2 = &mut SomeStruct; + forget(reference2); + + let ref reference3 = SomeStruct; + forget(reference3); +} + +#[allow(dead_code)] +fn test_generic_fn_forget(val: T) { + forget(&val); + forget(val); //OK +} + +#[allow(dead_code)] +fn test_similarly_named_function() { + fn forget(_val: T) {} + forget(&SomeStruct); //OK; call to unrelated function which happens to have the same name + std::mem::forget(&SomeStruct); +} + +#[derive(Copy, Clone)] +pub struct Error; +fn produce_half_owl_error() -> Result<(), Error> { + Ok(()) +} + +fn produce_half_owl_ok() -> Result { + Ok(true) +} diff --git a/src/tools/clippy/tests/ui/forget_ref.stderr b/src/tools/clippy/tests/ui/forget_ref.stderr new file mode 100644 index 0000000000000..f90bcc2762cee --- /dev/null +++ b/src/tools/clippy/tests/ui/forget_ref.stderr @@ -0,0 +1,111 @@ +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:9:5 + | +LL | forget(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::forget-ref` implied by `-D warnings` +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:9:12 + | +LL | forget(&SomeStruct); + | ^^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:12:5 + | +LL | forget(&owned); + | ^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:12:12 + | +LL | forget(&owned); + | ^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:13:5 + | +LL | forget(&&owned); + | ^^^^^^^^^^^^^^^ + | +note: argument has type `&&SomeStruct` + --> $DIR/forget_ref.rs:13:12 + | +LL | forget(&&owned); + | ^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:14:5 + | +LL | forget(&mut owned); + | ^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/forget_ref.rs:14:12 + | +LL | forget(&mut owned); + | ^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:18:5 + | +LL | forget(&*reference1); + | ^^^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:18:12 + | +LL | forget(&*reference1); + | ^^^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:21:5 + | +LL | forget(reference2); + | ^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&mut SomeStruct` + --> $DIR/forget_ref.rs:21:12 + | +LL | forget(reference2); + | ^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:24:5 + | +LL | forget(reference3); + | ^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:24:12 + | +LL | forget(reference3); + | ^^^^^^^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:29:5 + | +LL | forget(&val); + | ^^^^^^^^^^^^ + | +note: argument has type `&T` + --> $DIR/forget_ref.rs:29:12 + | +LL | forget(&val); + | ^^^^ + +error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. + --> $DIR/forget_ref.rs:37:5 + | +LL | std::mem::forget(&SomeStruct); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: argument has type `&SomeStruct` + --> $DIR/forget_ref.rs:37:22 + | +LL | std::mem::forget(&SomeStruct); + | ^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/format.fixed b/src/tools/clippy/tests/ui/format.fixed new file mode 100644 index 0000000000000..306514769990d --- /dev/null +++ b/src/tools/clippy/tests/ui/format.fixed @@ -0,0 +1,67 @@ +// run-rustfix + +#![allow(clippy::print_literal, clippy::redundant_clone)] +#![warn(clippy::useless_format)] + +struct Foo(pub String); + +macro_rules! foo { + ($($t:tt)*) => (Foo(format!($($t)*))) +} + +fn main() { + "foo".to_string(); + "{}".to_string(); + "{} abc {}".to_string(); + "foo {}\n\" bar".to_string(); + + "foo".to_string(); + format!("{:?}", "foo"); // Don't warn about `Debug`. + format!("{:8}", "foo"); + format!("{:width$}", "foo", width = 8); + "foo".to_string(); // Warn when the format makes no difference. + "foo".to_string(); // Warn when the format makes no difference. + format!("foo {}", "bar"); + format!("{} bar", "foo"); + + let arg: String = "".to_owned(); + arg.to_string(); + format!("{:?}", arg); // Don't warn about debug. + format!("{:8}", arg); + format!("{:width$}", arg, width = 8); + arg.to_string(); // Warn when the format makes no difference. + arg.to_string(); // Warn when the format makes no difference. + format!("foo {}", arg); + format!("{} bar", arg); + + // We don’t want to warn for non-string args; see issue #697. + format!("{}", 42); + format!("{:?}", 42); + format!("{:+}", 42); + format!("foo {}", 42); + format!("{} bar", 42); + + // We only want to warn about `format!` itself. + println!("foo"); + println!("{}", "foo"); + println!("foo {}", "foo"); + println!("{}", 42); + println!("foo {}", 42); + + // A `format!` inside a macro should not trigger a warning. + foo!("should not warn"); + + // Precision on string means slicing without panicking on size. + format!("{:.1}", "foo"); // Could be `"foo"[..1]` + format!("{:.10}", "foo"); // Could not be `"foo"[..10]` + format!("{:.prec$}", "foo", prec = 1); + format!("{:.prec$}", "foo", prec = 10); + + 42.to_string(); + let x = std::path::PathBuf::from("/bar/foo/qux"); + x.display().to_string(); + + // False positive + let a = "foo".to_string(); + let _ = Some(a + "bar"); +} diff --git a/src/tools/clippy/tests/ui/format.rs b/src/tools/clippy/tests/ui/format.rs new file mode 100644 index 0000000000000..b604d79cca373 --- /dev/null +++ b/src/tools/clippy/tests/ui/format.rs @@ -0,0 +1,70 @@ +// run-rustfix + +#![allow(clippy::print_literal, clippy::redundant_clone)] +#![warn(clippy::useless_format)] + +struct Foo(pub String); + +macro_rules! foo { + ($($t:tt)*) => (Foo(format!($($t)*))) +} + +fn main() { + format!("foo"); + format!("{{}}"); + format!("{{}} abc {{}}"); + format!( + r##"foo {{}} +" bar"## + ); + + format!("{}", "foo"); + format!("{:?}", "foo"); // Don't warn about `Debug`. + format!("{:8}", "foo"); + format!("{:width$}", "foo", width = 8); + format!("{:+}", "foo"); // Warn when the format makes no difference. + format!("{:<}", "foo"); // Warn when the format makes no difference. + format!("foo {}", "bar"); + format!("{} bar", "foo"); + + let arg: String = "".to_owned(); + format!("{}", arg); + format!("{:?}", arg); // Don't warn about debug. + format!("{:8}", arg); + format!("{:width$}", arg, width = 8); + format!("{:+}", arg); // Warn when the format makes no difference. + format!("{:<}", arg); // Warn when the format makes no difference. + format!("foo {}", arg); + format!("{} bar", arg); + + // We don’t want to warn for non-string args; see issue #697. + format!("{}", 42); + format!("{:?}", 42); + format!("{:+}", 42); + format!("foo {}", 42); + format!("{} bar", 42); + + // We only want to warn about `format!` itself. + println!("foo"); + println!("{}", "foo"); + println!("foo {}", "foo"); + println!("{}", 42); + println!("foo {}", 42); + + // A `format!` inside a macro should not trigger a warning. + foo!("should not warn"); + + // Precision on string means slicing without panicking on size. + format!("{:.1}", "foo"); // Could be `"foo"[..1]` + format!("{:.10}", "foo"); // Could not be `"foo"[..10]` + format!("{:.prec$}", "foo", prec = 1); + format!("{:.prec$}", "foo", prec = 10); + + format!("{}", 42.to_string()); + let x = std::path::PathBuf::from("/bar/foo/qux"); + format!("{}", x.display().to_string()); + + // False positive + let a = "foo".to_string(); + let _ = Some(format!("{}", a + "bar")); +} diff --git a/src/tools/clippy/tests/ui/format.stderr b/src/tools/clippy/tests/ui/format.stderr new file mode 100644 index 0000000000000..9734492154e80 --- /dev/null +++ b/src/tools/clippy/tests/ui/format.stderr @@ -0,0 +1,85 @@ +error: useless use of `format!` + --> $DIR/format.rs:13:5 + | +LL | format!("foo"); + | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + | + = note: `-D clippy::useless-format` implied by `-D warnings` + +error: useless use of `format!` + --> $DIR/format.rs:14:5 + | +LL | format!("{{}}"); + | ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:15:5 + | +LL | format!("{{}} abc {{}}"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:16:5 + | +LL | / format!( +LL | | r##"foo {{}} +LL | | " bar"## +LL | | ); + | |______^ help: consider using `.to_string()`: `"foo {}/n/" bar".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:21:5 + | +LL | format!("{}", "foo"); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:25:5 + | +LL | format!("{:+}", "foo"); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:26:5 + | +LL | format!("{:<}", "foo"); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:31:5 + | +LL | format!("{}", arg); + | ^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:35:5 + | +LL | format!("{:+}", arg); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:36:5 + | +LL | format!("{:<}", arg); // Warn when the format makes no difference. + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:63:5 + | +LL | format!("{}", 42.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:65:5 + | +LL | format!("{}", x.display().to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string();` + +error: useless use of `format!` + --> $DIR/format.rs:69:18 + | +LL | let _ = Some(format!("{}", a + "bar")); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/formatting.rs b/src/tools/clippy/tests/ui/formatting.rs new file mode 100644 index 0000000000000..078811b8d882b --- /dev/null +++ b/src/tools/clippy/tests/ui/formatting.rs @@ -0,0 +1,157 @@ +#![warn(clippy::all)] +#![allow(unused_variables)] +#![allow(unused_assignments)] +#![allow(clippy::if_same_then_else)] +#![allow(clippy::deref_addrof)] + +fn foo() -> bool { + true +} + +#[rustfmt::skip] +fn main() { + // weird `else` formatting: + if foo() { + } { + } + + if foo() { + } if foo() { + } + + let _ = { // if as the last expression + let _ = 0; + + if foo() { + } if foo() { + } + else { + } + }; + + let _ = { // if in the middle of a block + if foo() { + } if foo() { + } + else { + } + + let _ = 0; + }; + + if foo() { + } else + { + } + + if foo() { + } + else + { + } + + if foo() { + } else + if foo() { // the span of the above error should continue here + } + + if foo() { + } + else + if foo() { // the span of the above error should continue here + } + + // those are ok: + if foo() { + } + { + } + + if foo() { + } else { + } + + if foo() { + } + else { + } + + if foo() { + } + if foo() { + } + + if foo() { + } else if foo() { + } + + if foo() { + } + else if foo() { + } + + if foo() { + } + else if + foo() {} + + // weird op_eq formatting: + let mut a = 42; + a =- 35; + a =* &191; + + let mut b = true; + b =! false; + + // those are ok: + a = -35; + a = *&191; + b = !false; + + // possible missing comma in an array + let _ = &[ + -1, -2, -3 // <= no comma here + -4, -5, -6 + ]; + let _ = &[ + -1, -2, -3 // <= no comma here + *4, -5, -6 + ]; + + // those are ok: + let _ = &[ + -1, -2, -3, + -4, -5, -6 + ]; + let _ = &[ + -1, -2, -3, + -4, -5, -6, + ]; + let _ = &[ + 1 + 2, 3 + + 4, 5 + 6, + ]; + + // don't lint for bin op without unary equiv + // issue 3244 + vec![ + 1 + / 2, + ]; + // issue 3396 + vec![ + true + | false, + ]; + + // don't lint if the indentation suggests not to + let _ = &[ + 1 + 2, 3 + - 4, 5 + ]; + // lint if it doesnt + let _ = &[ + -1 + -4, + ]; +} diff --git a/src/tools/clippy/tests/ui/formatting.stderr b/src/tools/clippy/tests/ui/formatting.stderr new file mode 100644 index 0000000000000..e2095cc125bb7 --- /dev/null +++ b/src/tools/clippy/tests/ui/formatting.stderr @@ -0,0 +1,127 @@ +error: this looks like an `else {..}` but the `else` is missing + --> $DIR/formatting.rs:15:6 + | +LL | } { + | ^ + | + = note: `-D clippy::suspicious-else-formatting` implied by `-D warnings` + = note: to remove this lint, add the missing `else` or add a new line before the next block + +error: this looks like an `else if` but the `else` is missing + --> $DIR/formatting.rs:19:6 + | +LL | } if foo() { + | ^ + | + = note: to remove this lint, add the missing `else` or add a new line before the second `if` + +error: this looks like an `else if` but the `else` is missing + --> $DIR/formatting.rs:26:10 + | +LL | } if foo() { + | ^ + | + = note: to remove this lint, add the missing `else` or add a new line before the second `if` + +error: this looks like an `else if` but the `else` is missing + --> $DIR/formatting.rs:34:10 + | +LL | } if foo() { + | ^ + | + = note: to remove this lint, add the missing `else` or add a new line before the second `if` + +error: this is an `else {..}` but the formatting might hide it + --> $DIR/formatting.rs:43:6 + | +LL | } else + | ______^ +LL | | { + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `{..}` + +error: this is an `else {..}` but the formatting might hide it + --> $DIR/formatting.rs:48:6 + | +LL | } + | ______^ +LL | | else +LL | | { + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `{..}` + +error: this is an `else if` but the formatting might hide it + --> $DIR/formatting.rs:54:6 + | +LL | } else + | ______^ +LL | | if foo() { // the span of the above error should continue here + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `if` + +error: this is an `else if` but the formatting might hide it + --> $DIR/formatting.rs:59:6 + | +LL | } + | ______^ +LL | | else +LL | | if foo() { // the span of the above error should continue here + | |____^ + | + = note: to remove this lint, remove the `else` or remove the new line between `else` and `if` + +error: this looks like you are trying to use `.. -= ..`, but you really are doing `.. = (- ..)` + --> $DIR/formatting.rs:100:6 + | +LL | a =- 35; + | ^^^^ + | + = note: `-D clippy::suspicious-assignment-formatting` implied by `-D warnings` + = note: to remove this lint, use either `-=` or `= -` + +error: this looks like you are trying to use `.. *= ..`, but you really are doing `.. = (* ..)` + --> $DIR/formatting.rs:101:6 + | +LL | a =* &191; + | ^^^^ + | + = note: to remove this lint, use either `*=` or `= *` + +error: this looks like you are trying to use `.. != ..`, but you really are doing `.. = (! ..)` + --> $DIR/formatting.rs:104:6 + | +LL | b =! false; + | ^^^^ + | + = note: to remove this lint, use either `!=` or `= !` + +error: possibly missing a comma here + --> $DIR/formatting.rs:113:19 + | +LL | -1, -2, -3 // <= no comma here + | ^ + | + = note: `-D clippy::possible-missing-comma` implied by `-D warnings` + = note: to remove this lint, add a comma or write the expr in a single line + +error: possibly missing a comma here + --> $DIR/formatting.rs:117:19 + | +LL | -1, -2, -3 // <= no comma here + | ^ + | + = note: to remove this lint, add a comma or write the expr in a single line + +error: possibly missing a comma here + --> $DIR/formatting.rs:154:11 + | +LL | -1 + | ^ + | + = note: to remove this lint, add a comma or write the expr in a single line + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/functions.rs b/src/tools/clippy/tests/ui/functions.rs new file mode 100644 index 0000000000000..271754cb06ee7 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions.rs @@ -0,0 +1,104 @@ +#![warn(clippy::all)] +#![allow(dead_code)] +#![allow(unused_unsafe, clippy::missing_safety_doc)] + +// TOO_MANY_ARGUMENTS +fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {} + +fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + +#[rustfmt::skip] +fn bad_multiline( + one: u32, + two: u32, + three: &str, + four: bool, + five: f32, + six: f32, + seven: bool, + eight: () +) { + let _one = one; + let _two = two; + let _three = three; + let _four = four; + let _five = five; + let _six = six; + let _seven = seven; +} + +// don't lint extern fns +extern "C" fn extern_fn( + _one: u32, + _two: u32, + _three: *const u8, + _four: bool, + _five: f32, + _six: f32, + _seven: bool, + _eight: *const std::ffi::c_void, +) { +} + +pub trait Foo { + fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool); + fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()); + + fn ptr(p: *const u8); +} + +pub struct Bar; + +impl Bar { + fn good_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {} + fn bad_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} +} + +// ok, we don’t want to warn implementations +impl Foo for Bar { + fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {} + fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + + fn ptr(p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + unsafe { std::ptr::read(p) }; + } +} + +// NOT_UNSAFE_PTR_ARG_DEREF + +fn private(p: *const u8) { + println!("{}", unsafe { *p }); +} + +pub fn public(p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + unsafe { std::ptr::read(p) }; +} + +impl Bar { + fn private(self, p: *const u8) { + println!("{}", unsafe { *p }); + } + + pub fn public(self, p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + unsafe { std::ptr::read(p) }; + } + + pub fn public_ok(self, p: *const u8) { + if !p.is_null() { + println!("{:p}", p); + } + } + + pub unsafe fn public_unsafe(self, p: *const u8) { + println!("{}", unsafe { *p }); + println!("{:?}", unsafe { p.as_ref() }); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/functions.stderr b/src/tools/clippy/tests/ui/functions.stderr new file mode 100644 index 0000000000000..0a86568b18de9 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions.stderr @@ -0,0 +1,90 @@ +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:8:1 + | +LL | fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::too-many-arguments` implied by `-D warnings` + +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:11:1 + | +LL | / fn bad_multiline( +LL | | one: u32, +LL | | two: u32, +LL | | three: &str, +... | +LL | | eight: () +LL | | ) { + | |__^ + +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:45:5 + | +LL | fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this function has too many arguments (8/7) + --> $DIR/functions.rs:54:5 + | +LL | fn bad_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:63:34 + | +LL | println!("{}", unsafe { *p }); + | ^ + | + = note: `-D clippy::not-unsafe-ptr-arg-deref` implied by `-D warnings` + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:64:35 + | +LL | println!("{:?}", unsafe { p.as_ref() }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:65:33 + | +LL | unsafe { std::ptr::read(p) }; + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:76:30 + | +LL | println!("{}", unsafe { *p }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:77:31 + | +LL | println!("{:?}", unsafe { p.as_ref() }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:78:29 + | +LL | unsafe { std::ptr::read(p) }; + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:87:34 + | +LL | println!("{}", unsafe { *p }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:88:35 + | +LL | println!("{:?}", unsafe { p.as_ref() }); + | ^ + +error: this public function dereferences a raw pointer but is not marked `unsafe` + --> $DIR/functions.rs:89:33 + | +LL | unsafe { std::ptr::read(p) }; + | ^ + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/functions_maxlines.rs b/src/tools/clippy/tests/ui/functions_maxlines.rs new file mode 100644 index 0000000000000..5e1ee55e01040 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions_maxlines.rs @@ -0,0 +1,163 @@ +#![warn(clippy::too_many_lines)] + +fn good_lines() { + /* println!("This is good."); */ + // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* */ // println!("This is good."); + /* println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); */ + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); + println!("This is good."); +} + +fn bad_lines() { + println!("Dont get confused by braces: {{}}"); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/functions_maxlines.stderr b/src/tools/clippy/tests/ui/functions_maxlines.stderr new file mode 100644 index 0000000000000..9b0e7550cc314 --- /dev/null +++ b/src/tools/clippy/tests/ui/functions_maxlines.stderr @@ -0,0 +1,16 @@ +error: This function has a large number of lines. + --> $DIR/functions_maxlines.rs:58:1 + | +LL | / fn bad_lines() { +LL | | println!("Dont get confused by braces: {{}}"); +LL | | println!("This is bad."); +LL | | println!("This is bad."); +... | +LL | | println!("This is bad."); +LL | | } + | |_^ + | + = note: `-D clippy::too-many-lines` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/future_not_send.rs b/src/tools/clippy/tests/ui/future_not_send.rs new file mode 100644 index 0000000000000..6d09d71a630a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/future_not_send.rs @@ -0,0 +1,79 @@ +// edition:2018 +#![warn(clippy::future_not_send)] + +use std::cell::Cell; +use std::rc::Rc; +use std::sync::Arc; + +async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + async { true }.await +} + +pub async fn public_future(rc: Rc<[u8]>) { + async { true }.await; +} + +pub async fn public_send(arc: Arc<[u8]>) -> bool { + async { false }.await +} + +async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + true +} + +pub async fn public_future2(rc: Rc<[u8]>) {} + +pub async fn public_send2(arc: Arc<[u8]>) -> bool { + false +} + +struct Dummy { + rc: Rc<[u8]>, +} + +impl Dummy { + async fn private_future(&self) -> usize { + async { true }.await; + self.rc.len() + } + + pub async fn public_future(&self) { + self.private_future().await; + } + + pub fn public_send(&self) -> impl std::future::Future { + async { false } + } +} + +async fn generic_future(t: T) -> T +where + T: Send, +{ + let rt = &t; + async { true }.await; + t +} + +async fn generic_future_send(t: T) +where + T: Send, +{ + async { true }.await; +} + +async fn unclear_future(t: T) {} + +fn main() { + let rc = Rc::new([1, 2, 3]); + private_future(rc.clone(), &Cell::new(42)); + public_future(rc.clone()); + let arc = Arc::new([4, 5, 6]); + public_send(arc); + generic_future(42); + generic_future_send(42); + + let dummy = Dummy { rc }; + dummy.public_future(); + dummy.public_send(); +} diff --git a/src/tools/clippy/tests/ui/future_not_send.stderr b/src/tools/clippy/tests/ui/future_not_send.stderr new file mode 100644 index 0000000000000..3b4968ef0a63c --- /dev/null +++ b/src/tools/clippy/tests/ui/future_not_send.stderr @@ -0,0 +1,125 @@ +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:8:62 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ future returned by `private_future` is not `Send` + | + = note: `-D clippy::future-not-send` implied by `-D warnings` +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:9:5 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | -- has type `std::rc::Rc<[u8]>` which is not `Send` +LL | async { true }.await + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rc` maybe used later +LL | } + | - `rc` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:9:5 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ---- has type `&std::cell::Cell` which is not `Send` +LL | async { true }.await + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `cell` maybe used later +LL | } + | - `cell` is later dropped here + = note: `std::cell::Cell` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:12:42 + | +LL | pub async fn public_future(rc: Rc<[u8]>) { + | ^ future returned by `public_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:13:5 + | +LL | pub async fn public_future(rc: Rc<[u8]>) { + | -- has type `std::rc::Rc<[u8]>` which is not `Send` +LL | async { true }.await; + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rc` maybe used later +LL | } + | - `rc` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:20:63 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + = note: `std::cell::Cell` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:24:43 + | +LL | pub async fn public_future2(rc: Rc<[u8]>) {} + | ^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:35:39 + | +LL | async fn private_future(&self) -> usize { + | ^^^^^ future returned by `private_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:36:9 + | +LL | async fn private_future(&self) -> usize { + | ----- has type `&Dummy` which is not `Send` +LL | async { true }.await; + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `&self` maybe used later +LL | self.rc.len() +LL | } + | - `&self` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:40:39 + | +LL | pub async fn public_future(&self) { + | ^ future returned by `public_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:41:9 + | +LL | pub async fn public_future(&self) { + | ----- has type `&Dummy` which is not `Send` +LL | self.private_future().await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `&self` maybe used later +LL | } + | - `&self` is later dropped here + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:49:37 + | +LL | async fn generic_future(t: T) -> T + | ^ future returned by `generic_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> $DIR/future_not_send.rs:54:5 + | +LL | let rt = &t; + | -- has type `&T` which is not `Send` +LL | async { true }.await; + | ^^^^^^^^^^^^^^^^^^^^ await occurs here, with `rt` maybe used later +LL | t +LL | } + | - `rt` is later dropped here + = note: `T` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> $DIR/future_not_send.rs:65:34 + | +LL | async fn unclear_future(t: T) {} + | ^ + | + = note: `T` doesn't implement `std::marker::Send` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/get_last_with_len.fixed b/src/tools/clippy/tests/ui/get_last_with_len.fixed new file mode 100644 index 0000000000000..c8b363f9c38e6 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_last_with_len.fixed @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::get_last_with_len)] + +fn dont_use_last() { + let x = vec![2, 3, 5]; + let _ = x.last(); // ~ERROR Use x.last() +} + +fn indexing_two_from_end() { + let x = vec![2, 3, 5]; + let _ = x.get(x.len() - 2); +} + +fn index_into_last() { + let x = vec![2, 3, 5]; + let _ = x[x.len() - 1]; +} + +fn use_last_with_different_vec_length() { + let x = vec![2, 3, 5]; + let y = vec!['a', 'b', 'c']; + let _ = x.get(y.len() - 1); +} + +fn main() { + dont_use_last(); + indexing_two_from_end(); + index_into_last(); + use_last_with_different_vec_length(); +} diff --git a/src/tools/clippy/tests/ui/get_last_with_len.rs b/src/tools/clippy/tests/ui/get_last_with_len.rs new file mode 100644 index 0000000000000..bf9cb2d7e0ccd --- /dev/null +++ b/src/tools/clippy/tests/ui/get_last_with_len.rs @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::get_last_with_len)] + +fn dont_use_last() { + let x = vec![2, 3, 5]; + let _ = x.get(x.len() - 1); // ~ERROR Use x.last() +} + +fn indexing_two_from_end() { + let x = vec![2, 3, 5]; + let _ = x.get(x.len() - 2); +} + +fn index_into_last() { + let x = vec![2, 3, 5]; + let _ = x[x.len() - 1]; +} + +fn use_last_with_different_vec_length() { + let x = vec![2, 3, 5]; + let y = vec!['a', 'b', 'c']; + let _ = x.get(y.len() - 1); +} + +fn main() { + dont_use_last(); + indexing_two_from_end(); + index_into_last(); + use_last_with_different_vec_length(); +} diff --git a/src/tools/clippy/tests/ui/get_last_with_len.stderr b/src/tools/clippy/tests/ui/get_last_with_len.stderr new file mode 100644 index 0000000000000..55baf87384a24 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_last_with_len.stderr @@ -0,0 +1,10 @@ +error: accessing last element with `x.get(x.len() - 1)` + --> $DIR/get_last_with_len.rs:7:13 + | +LL | let _ = x.get(x.len() - 1); // ~ERROR Use x.last() + | ^^^^^^^^^^^^^^^^^^ help: try: `x.last()` + | + = note: `-D clippy::get-last-with-len` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/get_unwrap.fixed b/src/tools/clippy/tests/ui/get_unwrap.fixed new file mode 100644 index 0000000000000..97e6b20f471fc --- /dev/null +++ b/src/tools/clippy/tests/ui/get_unwrap.fixed @@ -0,0 +1,62 @@ +// run-rustfix +#![allow(unused_mut)] +#![deny(clippy::get_unwrap)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::iter::FromIterator; + +struct GetFalsePositive { + arr: [u32; 3], +} + +impl GetFalsePositive { + fn get(&self, pos: usize) -> Option<&u32> { + self.arr.get(pos) + } + fn get_mut(&mut self, pos: usize) -> Option<&mut u32> { + self.arr.get_mut(pos) + } +} + +fn main() { + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut some_slice = &mut [0, 1, 2, 3]; + let mut some_vec = vec![0, 1, 2, 3]; + let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect(); + let mut some_hashmap: HashMap = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut some_btreemap: BTreeMap = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut false_positive = GetFalsePositive { arr: [0, 1, 2] }; + + { + // Test `get().unwrap()` + let _ = &boxed_slice[1]; + let _ = &some_slice[0]; + let _ = &some_vec[0]; + let _ = &some_vecdeque[0]; + let _ = &some_hashmap[&1]; + let _ = &some_btreemap[&1]; + let _ = false_positive.get(0).unwrap(); + // Test with deref + let _: u8 = boxed_slice[1]; + } + + { + // Test `get_mut().unwrap()` + boxed_slice[0] = 1; + some_slice[0] = 1; + some_vec[0] = 1; + some_vecdeque[0] = 1; + // Check false positives + *some_hashmap.get_mut(&1).unwrap() = 'b'; + *some_btreemap.get_mut(&1).unwrap() = 'b'; + *false_positive.get_mut(0).unwrap() = 1; + } + + { + // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()` + let _ = some_vec[0..1].to_vec(); + let _ = some_vec[0..1].to_vec(); + } +} diff --git a/src/tools/clippy/tests/ui/get_unwrap.rs b/src/tools/clippy/tests/ui/get_unwrap.rs new file mode 100644 index 0000000000000..1c9a71c09699a --- /dev/null +++ b/src/tools/clippy/tests/ui/get_unwrap.rs @@ -0,0 +1,62 @@ +// run-rustfix +#![allow(unused_mut)] +#![deny(clippy::get_unwrap)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::iter::FromIterator; + +struct GetFalsePositive { + arr: [u32; 3], +} + +impl GetFalsePositive { + fn get(&self, pos: usize) -> Option<&u32> { + self.arr.get(pos) + } + fn get_mut(&mut self, pos: usize) -> Option<&mut u32> { + self.arr.get_mut(pos) + } +} + +fn main() { + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut some_slice = &mut [0, 1, 2, 3]; + let mut some_vec = vec![0, 1, 2, 3]; + let mut some_vecdeque: VecDeque<_> = some_vec.iter().cloned().collect(); + let mut some_hashmap: HashMap = HashMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut some_btreemap: BTreeMap = BTreeMap::from_iter(vec![(1, 'a'), (2, 'b')]); + let mut false_positive = GetFalsePositive { arr: [0, 1, 2] }; + + { + // Test `get().unwrap()` + let _ = boxed_slice.get(1).unwrap(); + let _ = some_slice.get(0).unwrap(); + let _ = some_vec.get(0).unwrap(); + let _ = some_vecdeque.get(0).unwrap(); + let _ = some_hashmap.get(&1).unwrap(); + let _ = some_btreemap.get(&1).unwrap(); + let _ = false_positive.get(0).unwrap(); + // Test with deref + let _: u8 = *boxed_slice.get(1).unwrap(); + } + + { + // Test `get_mut().unwrap()` + *boxed_slice.get_mut(0).unwrap() = 1; + *some_slice.get_mut(0).unwrap() = 1; + *some_vec.get_mut(0).unwrap() = 1; + *some_vecdeque.get_mut(0).unwrap() = 1; + // Check false positives + *some_hashmap.get_mut(&1).unwrap() = 'b'; + *some_btreemap.get_mut(&1).unwrap() = 'b'; + *false_positive.get_mut(0).unwrap() = 1; + } + + { + // Test `get().unwrap().foo()` and `get_mut().unwrap().bar()` + let _ = some_vec.get(0..1).unwrap().to_vec(); + let _ = some_vec.get_mut(0..1).unwrap().to_vec(); + } +} diff --git a/src/tools/clippy/tests/ui/get_unwrap.stderr b/src/tools/clippy/tests/ui/get_unwrap.stderr new file mode 100644 index 0000000000000..76a098df82aa1 --- /dev/null +++ b/src/tools/clippy/tests/ui/get_unwrap.stderr @@ -0,0 +1,86 @@ +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:34:17 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]` + | +note: the lint level is defined here + --> $DIR/get_unwrap.rs:3:9 + | +LL | #![deny(clippy::get_unwrap)] + | ^^^^^^^^^^^^^^^^^^ + +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:35:17 + | +LL | let _ = some_slice.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]` + +error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:36:17 + | +LL | let _ = some_vec.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]` + +error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:37:17 + | +LL | let _ = some_vecdeque.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]` + +error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:38:17 + | +LL | let _ = some_hashmap.get(&1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]` + +error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:39:17 + | +LL | let _ = some_btreemap.get(&1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]` + +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:42:21 + | +LL | let _: u8 = *boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]` + +error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:47:9 + | +LL | *boxed_slice.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]` + +error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:48:9 + | +LL | *some_slice.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]` + +error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:49:9 + | +LL | *some_vec.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]` + +error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:50:9 + | +LL | *some_vecdeque.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]` + +error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:59:17 + | +LL | let _ = some_vec.get(0..1).unwrap().to_vec(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` + +error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise + --> $DIR/get_unwrap.rs:60:17 + | +LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/identity_conversion.fixed b/src/tools/clippy/tests/ui/identity_conversion.fixed new file mode 100644 index 0000000000000..dd3fc56e98bcf --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_conversion.fixed @@ -0,0 +1,58 @@ +// run-rustfix + +#![deny(clippy::identity_conversion)] + +fn test_generic(val: T) -> T { + let _ = val; + val +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.into(); + let _: U = val.into(); + let _ = U::from(val); +} + +fn test_questionmark() -> Result<(), ()> { + { + let _: i32 = 0i32; + Ok(Ok(())) + }??; + Ok(()) +} + +fn test_issue_3913() -> Result<(), std::io::Error> { + use std::fs; + use std::path::Path; + + let path = Path::new("."); + for _ in fs::read_dir(path)? {} + + Ok(()) +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + test_questionmark().unwrap(); + test_issue_3913().unwrap(); + + let _: String = "foo".into(); + let _: String = From::from("foo"); + let _ = String::from("foo"); + #[allow(clippy::identity_conversion)] + { + let _: String = "foo".into(); + let _ = String::from("foo"); + let _ = "".lines().into_iter(); + } + + let _: String = "foo".to_string(); + let _: String = "foo".to_string(); + let _ = "foo".to_string(); + let _ = format!("A: {:04}", 123); + let _ = "".lines(); + let _ = vec![1, 2, 3].into_iter(); + let _: String = format!("Hello {}", "world"); +} diff --git a/src/tools/clippy/tests/ui/identity_conversion.rs b/src/tools/clippy/tests/ui/identity_conversion.rs new file mode 100644 index 0000000000000..875ed7db373b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_conversion.rs @@ -0,0 +1,58 @@ +// run-rustfix + +#![deny(clippy::identity_conversion)] + +fn test_generic(val: T) -> T { + let _ = T::from(val); + val.into() +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.into(); + let _: U = val.into(); + let _ = U::from(val); +} + +fn test_questionmark() -> Result<(), ()> { + { + let _: i32 = 0i32.into(); + Ok(Ok(())) + }??; + Ok(()) +} + +fn test_issue_3913() -> Result<(), std::io::Error> { + use std::fs; + use std::path::Path; + + let path = Path::new("."); + for _ in fs::read_dir(path)? {} + + Ok(()) +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + test_questionmark().unwrap(); + test_issue_3913().unwrap(); + + let _: String = "foo".into(); + let _: String = From::from("foo"); + let _ = String::from("foo"); + #[allow(clippy::identity_conversion)] + { + let _: String = "foo".into(); + let _ = String::from("foo"); + let _ = "".lines().into_iter(); + } + + let _: String = "foo".to_string().into(); + let _: String = From::from("foo".to_string()); + let _ = String::from("foo".to_string()); + let _ = String::from(format!("A: {:04}", 123)); + let _ = "".lines().into_iter(); + let _ = vec![1, 2, 3].into_iter().into_iter(); + let _: String = format!("Hello {}", "world").into(); +} diff --git a/src/tools/clippy/tests/ui/identity_conversion.stderr b/src/tools/clippy/tests/ui/identity_conversion.stderr new file mode 100644 index 0000000000000..57626b23795cf --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_conversion.stderr @@ -0,0 +1,68 @@ +error: identical conversion + --> $DIR/identity_conversion.rs:6:13 + | +LL | let _ = T::from(val); + | ^^^^^^^^^^^^ help: consider removing `T::from()`: `val` + | +note: the lint level is defined here + --> $DIR/identity_conversion.rs:3:9 + | +LL | #![deny(clippy::identity_conversion)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: identical conversion + --> $DIR/identity_conversion.rs:7:5 + | +LL | val.into() + | ^^^^^^^^^^ help: consider removing `.into()`: `val` + +error: identical conversion + --> $DIR/identity_conversion.rs:19:22 + | +LL | let _: i32 = 0i32.into(); + | ^^^^^^^^^^^ help: consider removing `.into()`: `0i32` + +error: identical conversion + --> $DIR/identity_conversion.rs:51:21 + | +LL | let _: String = "foo".to_string().into(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()` + +error: identical conversion + --> $DIR/identity_conversion.rs:52:21 + | +LL | let _: String = From::from("foo".to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()` + +error: identical conversion + --> $DIR/identity_conversion.rs:53:13 + | +LL | let _ = String::from("foo".to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()` + +error: identical conversion + --> $DIR/identity_conversion.rs:54:13 + | +LL | let _ = String::from(format!("A: {:04}", 123)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `format!("A: {:04}", 123)` + +error: identical conversion + --> $DIR/identity_conversion.rs:55:13 + | +LL | let _ = "".lines().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `"".lines()` + +error: identical conversion + --> $DIR/identity_conversion.rs:56:13 + | +LL | let _ = vec![1, 2, 3].into_iter().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2, 3].into_iter()` + +error: identical conversion + --> $DIR/identity_conversion.rs:57:21 + | +LL | let _: String = format!("Hello {}", "world").into(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `format!("Hello {}", "world")` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/identity_op.rs b/src/tools/clippy/tests/ui/identity_op.rs new file mode 100644 index 0000000000000..ae2815d345a94 --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_op.rs @@ -0,0 +1,36 @@ +const ONE: i64 = 1; +const NEG_ONE: i64 = -1; +const ZERO: i64 = 0; + +#[allow( + clippy::eq_op, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::double_parens +)] +#[warn(clippy::identity_op)] +#[rustfmt::skip] +fn main() { + let x = 0; + + x + 0; + x + (1 - 1); + x + 1; + 0 + x; + 1 + x; + x - ZERO; //no error, as we skip lookups (for now) + x | (0); + ((ZERO)) | x; //no error, as we skip lookups (for now) + + x * 1; + 1 * x; + x / ONE; //no error, as we skip lookups (for now) + + x / 2; //no false positive + + x & NEG_ONE; //no error, as we skip lookups (for now) + -1 & x; + + let u: u8 = 0; + u & 255; +} diff --git a/src/tools/clippy/tests/ui/identity_op.stderr b/src/tools/clippy/tests/ui/identity_op.stderr new file mode 100644 index 0000000000000..4742877706acd --- /dev/null +++ b/src/tools/clippy/tests/ui/identity_op.stderr @@ -0,0 +1,52 @@ +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:16:5 + | +LL | x + 0; + | ^^^^^ + | + = note: `-D clippy::identity-op` implied by `-D warnings` + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:17:5 + | +LL | x + (1 - 1); + | ^^^^^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:19:5 + | +LL | 0 + x; + | ^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:22:5 + | +LL | x | (0); + | ^^^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:25:5 + | +LL | x * 1; + | ^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:26:5 + | +LL | 1 * x; + | ^^^^^ + +error: the operation is ineffective. Consider reducing it to `x` + --> $DIR/identity_op.rs:32:5 + | +LL | -1 & x; + | ^^^^^^ + +error: the operation is ineffective. Consider reducing it to `u` + --> $DIR/identity_op.rs:35:5 + | +LL | u & 255; + | ^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/if_let_mutex.rs b/src/tools/clippy/tests/ui/if_let_mutex.rs new file mode 100644 index 0000000000000..58feae422a3c1 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_mutex.rs @@ -0,0 +1,42 @@ +#![warn(clippy::if_let_mutex)] + +use std::ops::Deref; +use std::sync::Mutex; + +fn do_stuff(_: T) {} + +fn if_let() { + let m = Mutex::new(1_u8); + if let Err(locked) = m.lock() { + do_stuff(locked); + } else { + let lock = m.lock().unwrap(); + do_stuff(lock); + }; +} + +// This is the most common case as the above case is pretty +// contrived. +fn if_let_option() { + let m = Mutex::new(Some(0_u8)); + if let Some(locked) = m.lock().unwrap().deref() { + do_stuff(locked); + } else { + let lock = m.lock().unwrap(); + do_stuff(lock); + }; +} + +// When mutexs are different don't warn +fn if_let_different_mutex() { + let m = Mutex::new(Some(0_u8)); + let other = Mutex::new(None::); + if let Some(locked) = m.lock().unwrap().deref() { + do_stuff(locked); + } else { + let lock = other.lock().unwrap(); + do_stuff(lock); + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/if_let_mutex.stderr b/src/tools/clippy/tests/ui/if_let_mutex.stderr new file mode 100644 index 0000000000000..e9c4d9163328f --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_mutex.stderr @@ -0,0 +1,29 @@ +error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock + --> $DIR/if_let_mutex.rs:10:5 + | +LL | / if let Err(locked) = m.lock() { +LL | | do_stuff(locked); +LL | | } else { +LL | | let lock = m.lock().unwrap(); +LL | | do_stuff(lock); +LL | | }; + | |_____^ + | + = note: `-D clippy::if-let-mutex` implied by `-D warnings` + = help: move the lock call outside of the `if let ...` expression + +error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock + --> $DIR/if_let_mutex.rs:22:5 + | +LL | / if let Some(locked) = m.lock().unwrap().deref() { +LL | | do_stuff(locked); +LL | | } else { +LL | | let lock = m.lock().unwrap(); +LL | | do_stuff(lock); +LL | | }; + | |_____^ + | + = help: move the lock call outside of the `if let ...` expression + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/if_let_some_result.fixed b/src/tools/clippy/tests/ui/if_let_some_result.fixed new file mode 100644 index 0000000000000..80505fd997f42 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_some_result.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::if_let_some_result)] + +fn str_to_int(x: &str) -> i32 { + if let Ok(y) = x.parse() { + y + } else { + 0 + } +} + +fn str_to_int_ok(x: &str) -> i32 { + if let Ok(y) = x.parse() { + y + } else { + 0 + } +} + +#[rustfmt::skip] +fn strange_some_no_else(x: &str) -> i32 { + { + if let Ok(y) = x . parse() { + return y; + }; + 0 + } +} + +fn main() { + let _ = str_to_int("1"); + let _ = str_to_int_ok("2"); + let _ = strange_some_no_else("3"); +} diff --git a/src/tools/clippy/tests/ui/if_let_some_result.rs b/src/tools/clippy/tests/ui/if_let_some_result.rs new file mode 100644 index 0000000000000..ecac13574456c --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_some_result.rs @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::if_let_some_result)] + +fn str_to_int(x: &str) -> i32 { + if let Some(y) = x.parse().ok() { + y + } else { + 0 + } +} + +fn str_to_int_ok(x: &str) -> i32 { + if let Ok(y) = x.parse() { + y + } else { + 0 + } +} + +#[rustfmt::skip] +fn strange_some_no_else(x: &str) -> i32 { + { + if let Some(y) = x . parse() . ok () { + return y; + }; + 0 + } +} + +fn main() { + let _ = str_to_int("1"); + let _ = str_to_int_ok("2"); + let _ = strange_some_no_else("3"); +} diff --git a/src/tools/clippy/tests/ui/if_let_some_result.stderr b/src/tools/clippy/tests/ui/if_let_some_result.stderr new file mode 100644 index 0000000000000..334ccb0101678 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_let_some_result.stderr @@ -0,0 +1,25 @@ +error: Matching on `Some` with `ok()` is redundant + --> $DIR/if_let_some_result.rs:6:5 + | +LL | if let Some(y) = x.parse().ok() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::if-let-some-result` implied by `-D warnings` +help: Consider matching on `Ok(y)` and removing the call to `ok` instead + | +LL | if let Ok(y) = x.parse() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Matching on `Some` with `ok()` is redundant + --> $DIR/if_let_some_result.rs:24:9 + | +LL | if let Some(y) = x . parse() . ok () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: Consider matching on `Ok(y)` and removing the call to `ok` instead + | +LL | if let Ok(y) = x . parse() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/if_not_else.rs b/src/tools/clippy/tests/ui/if_not_else.rs new file mode 100644 index 0000000000000..dc3fb1ceac9a4 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_not_else.rs @@ -0,0 +1,19 @@ +#![warn(clippy::all)] +#![warn(clippy::if_not_else)] + +fn bla() -> bool { + unimplemented!() +} + +fn main() { + if !bla() { + println!("Bugs"); + } else { + println!("Bunny"); + } + if 4 != 5 { + println!("Bugs"); + } else { + println!("Bunny"); + } +} diff --git a/src/tools/clippy/tests/ui/if_not_else.stderr b/src/tools/clippy/tests/ui/if_not_else.stderr new file mode 100644 index 0000000000000..78bc4d4bd20a3 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_not_else.stderr @@ -0,0 +1,27 @@ +error: Unnecessary boolean `not` operation + --> $DIR/if_not_else.rs:9:5 + | +LL | / if !bla() { +LL | | println!("Bugs"); +LL | | } else { +LL | | println!("Bunny"); +LL | | } + | |_____^ + | + = note: `-D clippy::if-not-else` implied by `-D warnings` + = help: remove the `!` and swap the blocks of the `if`/`else` + +error: Unnecessary `!=` operation + --> $DIR/if_not_else.rs:14:5 + | +LL | / if 4 != 5 { +LL | | println!("Bugs"); +LL | | } else { +LL | | println!("Bunny"); +LL | | } + | |_____^ + | + = help: change to `==` and swap the blocks of the `if`/`else` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/if_same_then_else.rs b/src/tools/clippy/tests/ui/if_same_then_else.rs new file mode 100644 index 0000000000000..6bbf79edfcf70 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else.rs @@ -0,0 +1,145 @@ +#![warn(clippy::if_same_then_else)] +#![allow( + clippy::blacklisted_name, + clippy::eq_op, + clippy::never_loop, + clippy::no_effect, + clippy::unused_unit, + clippy::zero_divided_by_zero +)] + +struct Foo { + bar: u8, +} + +fn foo() -> bool { + unimplemented!() +} + +fn if_same_then_else() { + if true { + Foo { bar: 42 }; + 0..10; + ..; + 0..; + ..10; + 0..=10; + foo(); + } else { + //~ ERROR same body as `if` block + Foo { bar: 42 }; + 0..10; + ..; + 0..; + ..10; + 0..=10; + foo(); + } + + if true { + Foo { bar: 42 }; + } else { + Foo { bar: 43 }; + } + + if true { + (); + } else { + () + } + + if true { + 0..10; + } else { + 0..=10; + } + + if true { + foo(); + foo(); + } else { + foo(); + } + + let _ = if true { + 0.0 + } else { + //~ ERROR same body as `if` block + 0.0 + }; + + let _ = if true { + -0.0 + } else { + //~ ERROR same body as `if` block + -0.0 + }; + + let _ = if true { 0.0 } else { -0.0 }; + + // Different NaNs + let _ = if true { 0.0 / 0.0 } else { f32::NAN }; + + if true { + foo(); + } + + let _ = if true { + 42 + } else { + //~ ERROR same body as `if` block + 42 + }; + + if true { + let bar = if true { 42 } else { 43 }; + + while foo() { + break; + } + bar + 1; + } else { + //~ ERROR same body as `if` block + let bar = if true { 42 } else { 43 }; + + while foo() { + break; + } + bar + 1; + } + + if true { + let _ = match 42 { + 42 => 1, + a if a > 0 => 2, + 10..=15 => 3, + _ => 4, + }; + } else if false { + foo(); + } else if foo() { + let _ = match 42 { + 42 => 1, + a if a > 0 => 2, + 10..=15 => 3, + _ => 4, + }; + } +} + +// Issue #2423. This was causing an ICE. +fn func() { + if true { + f(&[0; 62]); + f(&[0; 4]); + f(&[0; 3]); + } else { + f(&[0; 62]); + f(&[0; 6]); + f(&[0; 6]); + } +} + +fn f(val: &[u8]) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/if_same_then_else.stderr b/src/tools/clippy/tests/ui/if_same_then_else.stderr new file mode 100644 index 0000000000000..d9fdf06fa8b73 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else.stderr @@ -0,0 +1,112 @@ +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:28:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | Foo { bar: 42 }; +LL | | 0..10; +... | +LL | | foo(); +LL | | } + | |_____^ + | + = note: `-D clippy::if-same-then-else` implied by `-D warnings` +note: same as this + --> $DIR/if_same_then_else.rs:20:13 + | +LL | if true { + | _____________^ +LL | | Foo { bar: 42 }; +LL | | 0..10; +LL | | ..; +... | +LL | | foo(); +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:66:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | 0.0 +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:64:21 + | +LL | let _ = if true { + | _____________________^ +LL | | 0.0 +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:73:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | -0.0 +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:71:21 + | +LL | let _ = if true { + | _____________________^ +LL | | -0.0 +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:89:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | 42 +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:87:21 + | +LL | let _ = if true { + | _____________________^ +LL | | 42 +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:101:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | let bar = if true { 42 } else { 43 }; +LL | | +... | +LL | | bar + 1; +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else.rs:94:13 + | +LL | if true { + | _____________^ +LL | | let bar = if true { 42 } else { 43 }; +LL | | +LL | | while foo() { +... | +LL | | bar + 1; +LL | | } else { + | |_____^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/if_same_then_else2.rs b/src/tools/clippy/tests/ui/if_same_then_else2.rs new file mode 100644 index 0000000000000..3cc21809264f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else2.rs @@ -0,0 +1,139 @@ +#![warn(clippy::if_same_then_else)] +#![allow( + clippy::blacklisted_name, + clippy::collapsible_if, + clippy::ifs_same_cond, + clippy::needless_return +)] + +fn if_same_then_else2() -> Result<&'static str, ()> { + if true { + for _ in &[42] { + let foo: &Option<_> = &Some::(42); + if true { + break; + } else { + continue; + } + } + } else { + //~ ERROR same body as `if` block + for _ in &[42] { + let foo: &Option<_> = &Some::(42); + if true { + break; + } else { + continue; + } + } + } + + if true { + if let Some(a) = Some(42) {} + } else { + //~ ERROR same body as `if` block + if let Some(a) = Some(42) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + //~ ERROR same body as `if` block + if let (1, .., 3) = (1, 2, 3) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + if let (.., 3) = (1, 2, 3) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + if let (.., 4) = (1, 2, 3) {} + } + + if true { + if let (1, .., 3) = (1, 2, 3) {} + } else { + if let (.., 1, 3) = (1, 2, 3) {} + } + + if true { + if let Some(42) = None {} + } else { + if let Option::Some(42) = None {} + } + + if true { + if let Some(42) = None:: {} + } else { + if let Some(42) = None {} + } + + if true { + if let Some(42) = None:: {} + } else { + if let Some(42) = None:: {} + } + + if true { + if let Some(a) = Some(42) {} + } else { + if let Some(a) = Some(43) {} + } + + // Same NaNs + let _ = if true { + f32::NAN + } else { + //~ ERROR same body as `if` block + f32::NAN + }; + + if true { + Ok("foo")?; + } else { + //~ ERROR same body as `if` block + Ok("foo")?; + } + + if true { + let foo = ""; + return Ok(&foo[0..]); + } else if false { + let foo = "bar"; + return Ok(&foo[0..]); + } else { + let foo = ""; + return Ok(&foo[0..]); + } + + if true { + let foo = ""; + return Ok(&foo[0..]); + } else if false { + let foo = "bar"; + return Ok(&foo[0..]); + } else if true { + let foo = ""; + return Ok(&foo[0..]); + } else { + let foo = ""; + return Ok(&foo[0..]); + } + + // False positive `if_same_then_else`: `let (x, y)` vs. `let (y, x)`; see issue #3559. + if true { + let foo = ""; + let (x, y) = (1, 2); + return Ok(&foo[x..y]); + } else { + let foo = ""; + let (y, x) = (1, 2); + return Ok(&foo[x..y]); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/if_same_then_else2.stderr b/src/tools/clippy/tests/ui/if_same_then_else2.stderr new file mode 100644 index 0000000000000..f5d087fe12838 --- /dev/null +++ b/src/tools/clippy/tests/ui/if_same_then_else2.stderr @@ -0,0 +1,125 @@ +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:19:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | for _ in &[42] { +LL | | let foo: &Option<_> = &Some::(42); +... | +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::if-same-then-else` implied by `-D warnings` +note: same as this + --> $DIR/if_same_then_else2.rs:10:13 + | +LL | if true { + | _____________^ +LL | | for _ in &[42] { +LL | | let foo: &Option<_> = &Some::(42); +LL | | if true { +... | +LL | | } +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:33:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | if let Some(a) = Some(42) {} +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:31:13 + | +LL | if true { + | _____________^ +LL | | if let Some(a) = Some(42) {} +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:40:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | if let (1, .., 3) = (1, 2, 3) {} +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:38:13 + | +LL | if true { + | _____________^ +LL | | if let (1, .., 3) = (1, 2, 3) {} +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:90:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | f32::NAN +LL | | }; + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:88:21 + | +LL | let _ = if true { + | _____________________^ +LL | | f32::NAN +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:97:12 + | +LL | } else { + | ____________^ +LL | | //~ ERROR same body as `if` block +LL | | Ok("foo")?; +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:95:13 + | +LL | if true { + | _____________^ +LL | | Ok("foo")?; +LL | | } else { + | |_____^ + +error: this `if` has identical blocks + --> $DIR/if_same_then_else2.rs:122:12 + | +LL | } else { + | ____________^ +LL | | let foo = ""; +LL | | return Ok(&foo[0..]); +LL | | } + | |_____^ + | +note: same as this + --> $DIR/if_same_then_else2.rs:119:20 + | +LL | } else if true { + | ____________________^ +LL | | let foo = ""; +LL | | return Ok(&foo[0..]); +LL | | } else { + | |_____^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/ifs_same_cond.rs b/src/tools/clippy/tests/ui/ifs_same_cond.rs new file mode 100644 index 0000000000000..80e9839ff40bd --- /dev/null +++ b/src/tools/clippy/tests/ui/ifs_same_cond.rs @@ -0,0 +1,46 @@ +#![warn(clippy::ifs_same_cond)] +#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks + +fn ifs_same_cond() { + let a = 0; + let b = false; + + if b { + } else if b { + //~ ERROR ifs same condition + } + + if a == 1 { + } else if a == 1 { + //~ ERROR ifs same condition + } + + if 2 * a == 1 { + } else if 2 * a == 2 { + } else if 2 * a == 1 { + //~ ERROR ifs same condition + } else if a == 1 { + } + + // See #659 + if cfg!(feature = "feature1-659") { + 1 + } else if cfg!(feature = "feature2-659") { + 2 + } else { + 3 + }; + + let mut v = vec![1]; + if v.pop() == None { + // ok, functions + } else if v.pop() == None { + } + + if v.len() == 42 { + // ok, functions + } else if v.len() == 42 { + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/ifs_same_cond.stderr b/src/tools/clippy/tests/ui/ifs_same_cond.stderr new file mode 100644 index 0000000000000..0c8f49b8687f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/ifs_same_cond.stderr @@ -0,0 +1,39 @@ +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:9:15 + | +LL | } else if b { + | ^ + | + = note: `-D clippy::ifs-same-cond` implied by `-D warnings` +note: same as this + --> $DIR/ifs_same_cond.rs:8:8 + | +LL | if b { + | ^ + +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:14:15 + | +LL | } else if a == 1 { + | ^^^^^^ + | +note: same as this + --> $DIR/ifs_same_cond.rs:13:8 + | +LL | if a == 1 { + | ^^^^^^ + +error: this `if` has the same condition as a previous `if` + --> $DIR/ifs_same_cond.rs:20:15 + | +LL | } else if 2 * a == 1 { + | ^^^^^^^^^^ + | +note: same as this + --> $DIR/ifs_same_cond.rs:18:8 + | +LL | if 2 * a == 1 { + | ^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/impl.rs b/src/tools/clippy/tests/ui/impl.rs new file mode 100644 index 0000000000000..1c46e3a533782 --- /dev/null +++ b/src/tools/clippy/tests/ui/impl.rs @@ -0,0 +1,36 @@ +#![allow(dead_code)] +#![warn(clippy::multiple_inherent_impl)] + +struct MyStruct; + +impl MyStruct { + fn first() {} +} + +impl MyStruct { + fn second() {} +} + +impl<'a> MyStruct { + fn lifetimed() {} +} + +mod submod { + struct MyStruct; + impl MyStruct { + fn other() {} + } + + impl super::MyStruct { + fn third() {} + } +} + +use std::fmt; +impl fmt::Debug for MyStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MyStruct {{ }}") + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/impl.stderr b/src/tools/clippy/tests/ui/impl.stderr new file mode 100644 index 0000000000000..585d32845d290 --- /dev/null +++ b/src/tools/clippy/tests/ui/impl.stderr @@ -0,0 +1,35 @@ +error: Multiple implementations of this structure + --> $DIR/impl.rs:10:1 + | +LL | / impl MyStruct { +LL | | fn second() {} +LL | | } + | |_^ + | + = note: `-D clippy::multiple-inherent-impl` implied by `-D warnings` +note: First implementation here + --> $DIR/impl.rs:6:1 + | +LL | / impl MyStruct { +LL | | fn first() {} +LL | | } + | |_^ + +error: Multiple implementations of this structure + --> $DIR/impl.rs:24:5 + | +LL | / impl super::MyStruct { +LL | | fn third() {} +LL | | } + | |_____^ + | +note: First implementation here + --> $DIR/impl.rs:6:1 + | +LL | / impl MyStruct { +LL | | fn first() {} +LL | | } + | |_^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_hasher.rs b/src/tools/clippy/tests/ui/implicit_hasher.rs new file mode 100644 index 0000000000000..fdcc9a33f55fe --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_hasher.rs @@ -0,0 +1,99 @@ +// aux-build:implicit_hasher_macros.rs +#![deny(clippy::implicit_hasher)] +#![allow(unused)] + +#[macro_use] +extern crate implicit_hasher_macros; + +use std::cmp::Eq; +use std::collections::{HashMap, HashSet}; +use std::hash::{BuildHasher, Hash}; + +pub trait Foo: Sized { + fn make() -> (Self, Self); +} + +impl Foo for HashMap { + fn make() -> (Self, Self) { + // OK, don't suggest to modify these + let _: HashMap = HashMap::new(); + let _: HashSet = HashSet::new(); + + (HashMap::new(), HashMap::with_capacity(10)) + } +} +impl Foo for (HashMap,) { + fn make() -> (Self, Self) { + ((HashMap::new(),), (HashMap::with_capacity(10),)) + } +} +impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::new(), HashMap::with_capacity(10)) + } +} + +impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::default(), HashMap::with_capacity_and_hasher(10, S::default())) + } +} +impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::default(), HashMap::with_capacity_and_hasher(10, S::default())) + } +} + +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::new(), HashSet::with_capacity(10)) + } +} +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::new(), HashSet::with_capacity(10)) + } +} + +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::default(), HashSet::with_capacity_and_hasher(10, S::default())) + } +} +impl Foo for HashSet { + fn make() -> (Self, Self) { + (HashSet::default(), HashSet::with_capacity_and_hasher(10, S::default())) + } +} + +pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + +macro_rules! gen { + (impl) => { + impl Foo for HashMap { + fn make() -> (Self, Self) { + (HashMap::new(), HashMap::with_capacity(10)) + } + } + }; + + (fn $name:ident) => { + pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + }; +} +#[rustfmt::skip] +gen!(impl); +gen!(fn bar); + +// When the macro is in a different file, the suggestion spans can't be combined properly +// and should not cause an ICE +// See #2707 +#[macro_use] +#[path = "../auxiliary/test_macro.rs"] +pub mod test_macro; +__implicit_hasher_test_macro!(impl for HashMap where V: test_macro::A); + +// #4260 +implicit_hasher_fn!(); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/implicit_hasher.stderr b/src/tools/clippy/tests/ui/implicit_hasher.stderr new file mode 100644 index 0000000000000..2b06d661772d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_hasher.stderr @@ -0,0 +1,153 @@ +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:16:35 + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/implicit_hasher.rs:2:9 + | +LL | #![deny(clippy::implicit_hasher)] + | ^^^^^^^^^^^^^^^^^^^^^^^ +help: consider adding a type parameter + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:25:36 + | +LL | impl Foo for (HashMap,) { + | ^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for (HashMap,) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | ((HashMap::default(),), (HashMap::with_capacity_and_hasher(10, Default::default()),)) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:30:19 + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:47:32 + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashSet::default(), HashSet::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: impl for `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:52:19 + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | impl Foo for HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashSet::default(), HashSet::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:69:23 + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:69:53 + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^ + | +help: consider adding a type parameter + | +LL | pub fn foo(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ + +error: impl for `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:73:43 + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^ +... +LL | gen!(impl); + | ----------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider adding a type parameter + | +LL | impl Foo for HashMap { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ +help: ...and use generic constructor + | +LL | (HashMap::default(), HashMap::with_capacity_and_hasher(10, Default::default())) + | ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashMap` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:81:33 + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^ +... +LL | gen!(fn bar); + | ------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider adding a type parameter + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + +error: parameter of type `HashSet` should be generalized over different hashers + --> $DIR/implicit_hasher.rs:81:63 + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^ +... +LL | gen!(fn bar); + | ------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider adding a type parameter + | +LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_return.fixed b/src/tools/clippy/tests/ui/implicit_return.fixed new file mode 100644 index 0000000000000..9066dc3fedfd6 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_return.fixed @@ -0,0 +1,101 @@ +// run-rustfix + +#![warn(clippy::implicit_return)] +#![allow(clippy::needless_return, unused)] + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + + return true +} + +#[allow(clippy::needless_bool)] +fn test_if_block() -> bool { + if true { + return true + } else { + return false + } +} + +#[rustfmt::skip] +fn test_match(x: bool) -> bool { + match x { + true => return false, + false => { return true }, + } +} + +#[allow(clippy::needless_return)] +fn test_match_with_unreachable(x: bool) -> bool { + match x { + true => return false, + false => unreachable!(), + } +} + +#[allow(clippy::never_loop)] +fn test_loop() -> bool { + loop { + return true; + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_block() -> bool { + loop { + { + return true; + } + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_nests() -> bool { + loop { + if true { + return true; + } else { + let _ = true; + } + } +} + +#[allow(clippy::redundant_pattern_matching)] +fn test_loop_with_if_let() -> bool { + loop { + if let Some(x) = Some(true) { + return x; + } + } +} + +fn test_closure() { + #[rustfmt::skip] + let _ = || { return true }; + let _ = || return true; +} + +fn test_panic() -> bool { + panic!() +} + +fn test_return_macro() -> String { + return format!("test {}", "test") +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_if_block(); + let _ = test_match(true); + let _ = test_match_with_unreachable(true); + let _ = test_loop(); + let _ = test_loop_with_block(); + let _ = test_loop_with_nests(); + let _ = test_loop_with_if_let(); + test_closure(); + let _ = test_return_macro(); +} diff --git a/src/tools/clippy/tests/ui/implicit_return.rs b/src/tools/clippy/tests/ui/implicit_return.rs new file mode 100644 index 0000000000000..c0d70ecf502ed --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_return.rs @@ -0,0 +1,101 @@ +// run-rustfix + +#![warn(clippy::implicit_return)] +#![allow(clippy::needless_return, unused)] + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + + true +} + +#[allow(clippy::needless_bool)] +fn test_if_block() -> bool { + if true { + true + } else { + false + } +} + +#[rustfmt::skip] +fn test_match(x: bool) -> bool { + match x { + true => false, + false => { true }, + } +} + +#[allow(clippy::needless_return)] +fn test_match_with_unreachable(x: bool) -> bool { + match x { + true => return false, + false => unreachable!(), + } +} + +#[allow(clippy::never_loop)] +fn test_loop() -> bool { + loop { + break true; + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_block() -> bool { + loop { + { + break true; + } + } +} + +#[allow(clippy::never_loop)] +fn test_loop_with_nests() -> bool { + loop { + if true { + break true; + } else { + let _ = true; + } + } +} + +#[allow(clippy::redundant_pattern_matching)] +fn test_loop_with_if_let() -> bool { + loop { + if let Some(x) = Some(true) { + return x; + } + } +} + +fn test_closure() { + #[rustfmt::skip] + let _ = || { true }; + let _ = || true; +} + +fn test_panic() -> bool { + panic!() +} + +fn test_return_macro() -> String { + format!("test {}", "test") +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_if_block(); + let _ = test_match(true); + let _ = test_match_with_unreachable(true); + let _ = test_loop(); + let _ = test_loop_with_block(); + let _ = test_loop_with_nests(); + let _ = test_loop_with_if_let(); + test_closure(); + let _ = test_return_macro(); +} diff --git a/src/tools/clippy/tests/ui/implicit_return.stderr b/src/tools/clippy/tests/ui/implicit_return.stderr new file mode 100644 index 0000000000000..fb2ec90276454 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_return.stderr @@ -0,0 +1,70 @@ +error: missing `return` statement + --> $DIR/implicit_return.rs:12:5 + | +LL | true + | ^^^^ help: add `return` as shown: `return true` + | + = note: `-D clippy::implicit-return` implied by `-D warnings` + +error: missing `return` statement + --> $DIR/implicit_return.rs:18:9 + | +LL | true + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:20:9 + | +LL | false + | ^^^^^ help: add `return` as shown: `return false` + +error: missing `return` statement + --> $DIR/implicit_return.rs:27:17 + | +LL | true => false, + | ^^^^^ help: add `return` as shown: `return false` + +error: missing `return` statement + --> $DIR/implicit_return.rs:28:20 + | +LL | false => { true }, + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:43:9 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:51:13 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:60:13 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:78:18 + | +LL | let _ = || { true }; + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:79:16 + | +LL | let _ = || true; + | ^^^^ help: add `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:87:5 + | +LL | format!("test {}", "test") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/implicit_saturating_sub.fixed b/src/tools/clippy/tests/ui/implicit_saturating_sub.fixed new file mode 100644 index 0000000000000..e0b5b31a00c7c --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_saturating_sub.fixed @@ -0,0 +1,160 @@ +// run-rustfix +#![allow(unused_assignments, unused_mut, clippy::assign_op_pattern)] +#![warn(clippy::implicit_saturating_sub)] + +fn main() { + // Tests for unsigned integers + + let end_8: u8 = 10; + let start_8: u8 = 5; + let mut u_8: u8 = end_8 - start_8; + + // Lint + u_8 = u_8.saturating_sub(1); + + match end_8 { + 10 => { + // Lint + u_8 = u_8.saturating_sub(1); + }, + 11 => u_8 += 1, + _ => u_8 = 0, + } + + let end_16: u16 = 40; + let start_16: u16 = 35; + + let mut u_16: u16 = end_16 - start_16; + + // Lint + u_16 = u_16.saturating_sub(1); + + let mut end_32: u32 = 7000; + let mut start_32: u32 = 7010; + + let mut u_32: u32 = end_32 - start_32; + + // Lint + u_32 = u_32.saturating_sub(1); + + // No Lint + if u_32 > 0 { + u_16 += 1; + } + + // No Lint + if u_32 != 0 { + end_32 -= 1; + start_32 += 1; + } + + let mut end_64: u64 = 75001; + let mut start_64: u64 = 75000; + + let mut u_64: u64 = end_64 - start_64; + + // Lint + u_64 = u_64.saturating_sub(1); + + // Lint + u_64 = u_64.saturating_sub(1); + + // Lint + u_64 = u_64.saturating_sub(1); + + // No Lint + if u_64 >= 1 { + u_64 -= 1; + } + + // No Lint + if u_64 > 0 { + end_64 -= 1; + } + + // Tests for usize + let end_usize: usize = 8054; + let start_usize: usize = 8050; + + let mut u_usize: usize = end_usize - start_usize; + + // Lint + u_usize = u_usize.saturating_sub(1); + + // Tests for signed integers + + let endi_8: i8 = 10; + let starti_8: i8 = 50; + + let mut i_8: i8 = endi_8 - starti_8; + + // Lint + i_8 = i_8.saturating_sub(1); + + // Lint + i_8 = i_8.saturating_sub(1); + + // Lint + i_8 = i_8.saturating_sub(1); + + // Lint + i_8 = i_8.saturating_sub(1); + + let endi_16: i16 = 45; + let starti_16: i16 = 44; + + let mut i_16: i16 = endi_16 - starti_16; + + // Lint + i_16 = i_16.saturating_sub(1); + + // Lint + i_16 = i_16.saturating_sub(1); + + // Lint + i_16 = i_16.saturating_sub(1); + + // Lint + i_16 = i_16.saturating_sub(1); + + let endi_32: i32 = 45; + let starti_32: i32 = 44; + + let mut i_32: i32 = endi_32 - starti_32; + + // Lint + i_32 = i_32.saturating_sub(1); + + // Lint + i_32 = i_32.saturating_sub(1); + + // Lint + i_32 = i_32.saturating_sub(1); + + // Lint + i_32 = i_32.saturating_sub(1); + + let endi_64: i64 = 45; + let starti_64: i64 = 44; + + let mut i_64: i64 = endi_64 - starti_64; + + // Lint + i_64 = i_64.saturating_sub(1); + + // Lint + i_64 = i_64.saturating_sub(1); + + // Lint + i_64 = i_64.saturating_sub(1); + + // No Lint + if i_64 > 0 { + i_64 -= 1; + } + + // No Lint + if i_64 != 0 { + i_64 -= 1; + } +} diff --git a/src/tools/clippy/tests/ui/implicit_saturating_sub.rs b/src/tools/clippy/tests/ui/implicit_saturating_sub.rs new file mode 100644 index 0000000000000..39d8160892297 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_saturating_sub.rs @@ -0,0 +1,206 @@ +// run-rustfix +#![allow(unused_assignments, unused_mut, clippy::assign_op_pattern)] +#![warn(clippy::implicit_saturating_sub)] + +fn main() { + // Tests for unsigned integers + + let end_8: u8 = 10; + let start_8: u8 = 5; + let mut u_8: u8 = end_8 - start_8; + + // Lint + if u_8 > 0 { + u_8 = u_8 - 1; + } + + match end_8 { + 10 => { + // Lint + if u_8 > 0 { + u_8 -= 1; + } + }, + 11 => u_8 += 1, + _ => u_8 = 0, + } + + let end_16: u16 = 40; + let start_16: u16 = 35; + + let mut u_16: u16 = end_16 - start_16; + + // Lint + if u_16 > 0 { + u_16 -= 1; + } + + let mut end_32: u32 = 7000; + let mut start_32: u32 = 7010; + + let mut u_32: u32 = end_32 - start_32; + + // Lint + if u_32 != 0 { + u_32 -= 1; + } + + // No Lint + if u_32 > 0 { + u_16 += 1; + } + + // No Lint + if u_32 != 0 { + end_32 -= 1; + start_32 += 1; + } + + let mut end_64: u64 = 75001; + let mut start_64: u64 = 75000; + + let mut u_64: u64 = end_64 - start_64; + + // Lint + if u_64 > 0 { + u_64 -= 1; + } + + // Lint + if 0 < u_64 { + u_64 -= 1; + } + + // Lint + if 0 != u_64 { + u_64 -= 1; + } + + // No Lint + if u_64 >= 1 { + u_64 -= 1; + } + + // No Lint + if u_64 > 0 { + end_64 -= 1; + } + + // Tests for usize + let end_usize: usize = 8054; + let start_usize: usize = 8050; + + let mut u_usize: usize = end_usize - start_usize; + + // Lint + if u_usize > 0 { + u_usize -= 1; + } + + // Tests for signed integers + + let endi_8: i8 = 10; + let starti_8: i8 = 50; + + let mut i_8: i8 = endi_8 - starti_8; + + // Lint + if i_8 > i8::MIN { + i_8 -= 1; + } + + // Lint + if i_8 > i8::min_value() { + i_8 -= 1; + } + + // Lint + if i_8 != i8::MIN { + i_8 -= 1; + } + + // Lint + if i_8 != i8::min_value() { + i_8 -= 1; + } + + let endi_16: i16 = 45; + let starti_16: i16 = 44; + + let mut i_16: i16 = endi_16 - starti_16; + + // Lint + if i_16 > i16::MIN { + i_16 -= 1; + } + + // Lint + if i_16 > i16::min_value() { + i_16 -= 1; + } + + // Lint + if i_16 != i16::MIN { + i_16 -= 1; + } + + // Lint + if i_16 != i16::min_value() { + i_16 -= 1; + } + + let endi_32: i32 = 45; + let starti_32: i32 = 44; + + let mut i_32: i32 = endi_32 - starti_32; + + // Lint + if i_32 > i32::MIN { + i_32 -= 1; + } + + // Lint + if i_32 > i32::min_value() { + i_32 -= 1; + } + + // Lint + if i_32 != i32::MIN { + i_32 -= 1; + } + + // Lint + if i_32 != i32::min_value() { + i_32 -= 1; + } + + let endi_64: i64 = 45; + let starti_64: i64 = 44; + + let mut i_64: i64 = endi_64 - starti_64; + + // Lint + if i64::min_value() < i_64 { + i_64 -= 1; + } + + // Lint + if i64::MIN != i_64 { + i_64 -= 1; + } + + // Lint + if i64::MIN < i_64 { + i_64 -= 1; + } + + // No Lint + if i_64 > 0 { + i_64 -= 1; + } + + // No Lint + if i_64 != 0 { + i_64 -= 1; + } +} diff --git a/src/tools/clippy/tests/ui/implicit_saturating_sub.stderr b/src/tools/clippy/tests/ui/implicit_saturating_sub.stderr new file mode 100644 index 0000000000000..a8ba870b1dda6 --- /dev/null +++ b/src/tools/clippy/tests/ui/implicit_saturating_sub.stderr @@ -0,0 +1,188 @@ +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:13:5 + | +LL | / if u_8 > 0 { +LL | | u_8 = u_8 - 1; +LL | | } + | |_____^ help: try: `u_8 = u_8.saturating_sub(1);` + | + = note: `-D clippy::implicit-saturating-sub` implied by `-D warnings` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:20:13 + | +LL | / if u_8 > 0 { +LL | | u_8 -= 1; +LL | | } + | |_____________^ help: try: `u_8 = u_8.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:34:5 + | +LL | / if u_16 > 0 { +LL | | u_16 -= 1; +LL | | } + | |_____^ help: try: `u_16 = u_16.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:44:5 + | +LL | / if u_32 != 0 { +LL | | u_32 -= 1; +LL | | } + | |_____^ help: try: `u_32 = u_32.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:65:5 + | +LL | / if u_64 > 0 { +LL | | u_64 -= 1; +LL | | } + | |_____^ help: try: `u_64 = u_64.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:70:5 + | +LL | / if 0 < u_64 { +LL | | u_64 -= 1; +LL | | } + | |_____^ help: try: `u_64 = u_64.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:75:5 + | +LL | / if 0 != u_64 { +LL | | u_64 -= 1; +LL | | } + | |_____^ help: try: `u_64 = u_64.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:96:5 + | +LL | / if u_usize > 0 { +LL | | u_usize -= 1; +LL | | } + | |_____^ help: try: `u_usize = u_usize.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:108:5 + | +LL | / if i_8 > i8::MIN { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:113:5 + | +LL | / if i_8 > i8::min_value() { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:118:5 + | +LL | / if i_8 != i8::MIN { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:123:5 + | +LL | / if i_8 != i8::min_value() { +LL | | i_8 -= 1; +LL | | } + | |_____^ help: try: `i_8 = i_8.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:133:5 + | +LL | / if i_16 > i16::MIN { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:138:5 + | +LL | / if i_16 > i16::min_value() { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:143:5 + | +LL | / if i_16 != i16::MIN { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:148:5 + | +LL | / if i_16 != i16::min_value() { +LL | | i_16 -= 1; +LL | | } + | |_____^ help: try: `i_16 = i_16.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:158:5 + | +LL | / if i_32 > i32::MIN { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:163:5 + | +LL | / if i_32 > i32::min_value() { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:168:5 + | +LL | / if i_32 != i32::MIN { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:173:5 + | +LL | / if i_32 != i32::min_value() { +LL | | i_32 -= 1; +LL | | } + | |_____^ help: try: `i_32 = i_32.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:183:5 + | +LL | / if i64::min_value() < i_64 { +LL | | i_64 -= 1; +LL | | } + | |_____^ help: try: `i_64 = i_64.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:188:5 + | +LL | / if i64::MIN != i_64 { +LL | | i_64 -= 1; +LL | | } + | |_____^ help: try: `i_64 = i_64.saturating_sub(1);` + +error: Implicitly performing saturating subtraction + --> $DIR/implicit_saturating_sub.rs:193:5 + | +LL | / if i64::MIN < i_64 { +LL | | i_64 -= 1; +LL | | } + | |_____^ help: try: `i_64 = i_64.saturating_sub(1);` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/inconsistent_digit_grouping.fixed b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.fixed new file mode 100644 index 0000000000000..b75f10917df18 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.fixed @@ -0,0 +1,43 @@ +// run-rustfix +#[warn(clippy::inconsistent_digit_grouping)] +#[deny(clippy::unreadable_literal)] +#[allow(unused_variables, clippy::excessive_precision)] +fn main() { + macro_rules! mac1 { + () => { + 1_23_456 + }; + } + macro_rules! mac2 { + () => { + 1_234.5678_f32 + }; + } + + let good = ( + 123, + 1_234, + 1_2345_6789, + 123_f32, + 1_234.12_f32, + 1_234.123_4_f32, + 1.123_456_7_f32, + ); + let bad = (123_456, 12_345_678, 1_234_567, 1_234.567_8_f32, 1.234_567_8_f32); + + // Test padding + let _ = 0x0010_0000; + let _ = 0x0100_0000; + let _ = 0x1000_0000; + let _ = 0x0001_0000_0000_u64; + + // Test suggestion when fraction has no digits + let _: f32 = 123_456.; + + // Test UUID formatted literal + let _: u128 = 0x12345678_1234_1234_1234_123456789012; + + // Ignore literals in macros + let _ = mac1!(); + let _ = mac2!(); +} diff --git a/src/tools/clippy/tests/ui/inconsistent_digit_grouping.rs b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.rs new file mode 100644 index 0000000000000..79ce38be19bd3 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.rs @@ -0,0 +1,43 @@ +// run-rustfix +#[warn(clippy::inconsistent_digit_grouping)] +#[deny(clippy::unreadable_literal)] +#[allow(unused_variables, clippy::excessive_precision)] +fn main() { + macro_rules! mac1 { + () => { + 1_23_456 + }; + } + macro_rules! mac2 { + () => { + 1_234.5678_f32 + }; + } + + let good = ( + 123, + 1_234, + 1_2345_6789, + 123_f32, + 1_234.12_f32, + 1_234.123_4_f32, + 1.123_456_7_f32, + ); + let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + + // Test padding + let _ = 0x100000; + let _ = 0x1000000; + let _ = 0x10000000; + let _ = 0x100000000_u64; + + // Test suggestion when fraction has no digits + let _: f32 = 1_23_456.; + + // Test UUID formatted literal + let _: u128 = 0x12345678_1234_1234_1234_123456789012; + + // Ignore literals in macros + let _ = mac1!(); + let _ = mac2!(); +} diff --git a/src/tools/clippy/tests/ui/inconsistent_digit_grouping.stderr b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.stderr new file mode 100644 index 0000000000000..b8ac915546200 --- /dev/null +++ b/src/tools/clippy/tests/ui/inconsistent_digit_grouping.stderr @@ -0,0 +1,70 @@ +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:16 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^ help: consider: `123_456` + | + = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:26 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^^^ help: consider: `12_345_678` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:38 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^ help: consider: `1_234_567` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:48 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^^^^^^^ help: consider: `1_234.567_8_f32` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:26:64 + | +LL | let bad = (1_23_456, 1_234_5678, 1234_567, 1_234.5678_f32, 1.234_5678_f32); + | ^^^^^^^^^^^^^^ help: consider: `1.234_567_8_f32` + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:29:13 + | +LL | let _ = 0x100000; + | ^^^^^^^^ help: consider: `0x0010_0000` + | +note: the lint level is defined here + --> $DIR/inconsistent_digit_grouping.rs:3:8 + | +LL | #[deny(clippy::unreadable_literal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:30:13 + | +LL | let _ = 0x1000000; + | ^^^^^^^^^ help: consider: `0x0100_0000` + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:31:13 + | +LL | let _ = 0x10000000; + | ^^^^^^^^^^ help: consider: `0x1000_0000` + +error: long literal lacking separators + --> $DIR/inconsistent_digit_grouping.rs:32:13 + | +LL | let _ = 0x100000000_u64; + | ^^^^^^^^^^^^^^^ help: consider: `0x0001_0000_0000_u64` + +error: digits grouped inconsistently by underscores + --> $DIR/inconsistent_digit_grouping.rs:35:18 + | +LL | let _: f32 = 1_23_456.; + | ^^^^^^^^^ help: consider: `123_456.` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/indexing_slicing_index.rs b/src/tools/clippy/tests/ui/indexing_slicing_index.rs new file mode 100644 index 0000000000000..000d5269930ba --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_index.rs @@ -0,0 +1,31 @@ +#![warn(clippy::indexing_slicing)] +// We also check the out_of_bounds_indexing lint here, because it lints similar things and +// we want to avoid false positives. +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + let x = [1, 2, 3, 4]; + let index: usize = 1; + x[index]; + x[4]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + x[1 << 3]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + + x[0]; // Ok, should not produce stderr. + x[3]; // Ok, should not produce stderr. + + let y = &x; + y[0]; + + let v = vec![0; 5]; + v[0]; + v[10]; + v[1 << 3]; + + const N: usize = 15; // Out of bounds + const M: usize = 3; // In bounds + x[N]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + x[M]; // Ok, should not produce stderr. + v[N]; + v[M]; +} diff --git a/src/tools/clippy/tests/ui/indexing_slicing_index.stderr b/src/tools/clippy/tests/ui/indexing_slicing_index.stderr new file mode 100644 index 0000000000000..ac5f0d0a39e89 --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_index.stderr @@ -0,0 +1,79 @@ +error: this operation will panic at runtime + --> $DIR/indexing_slicing_index.rs:11:5 + | +LL | x[4]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + | ^^^^ index out of bounds: the len is 4 but the index is 4 + | + = note: `#[deny(unconditional_panic)]` on by default + +error: this operation will panic at runtime + --> $DIR/indexing_slicing_index.rs:12:5 + | +LL | x[1 << 3]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + | ^^^^^^^^^ index out of bounds: the len is 4 but the index is 8 + +error: this operation will panic at runtime + --> $DIR/indexing_slicing_index.rs:27:5 + | +LL | x[N]; // Ok, let rustc's `const_err` lint handle `usize` indexing on arrays. + | ^^^^ index out of bounds: the len is 4 but the index is 15 + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:10:5 + | +LL | x[index]; + | ^^^^^^^^ + | + = note: `-D clippy::indexing-slicing` implied by `-D warnings` + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:18:5 + | +LL | y[0]; + | ^^^^ + | + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:21:5 + | +LL | v[0]; + | ^^^^ + | + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:22:5 + | +LL | v[10]; + | ^^^^^ + | + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:23:5 + | +LL | v[1 << 3]; + | ^^^^^^^^^ + | + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:29:5 + | +LL | v[N]; + | ^^^^ + | + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: indexing may panic. + --> $DIR/indexing_slicing_index.rs:30:5 + | +LL | v[M]; + | ^^^^ + | + = help: Consider using `.get(n)` or `.get_mut(n)` instead + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/indexing_slicing_slice.rs b/src/tools/clippy/tests/ui/indexing_slicing_slice.rs new file mode 100644 index 0000000000000..7b107db39f022 --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_slice.rs @@ -0,0 +1,37 @@ +#![warn(clippy::indexing_slicing)] +// We also check the out_of_bounds_indexing lint here, because it lints similar things and +// we want to avoid false positives. +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + let x = [1, 2, 3, 4]; + let index: usize = 1; + let index_from: usize = 2; + let index_to: usize = 3; + &x[index..]; + &x[..index]; + &x[index_from..index_to]; + &x[index_from..][..index_to]; // Two lint reports, one for [index_from..] and another for [..index_to]. + &x[5..][..10]; // Two lint reports, one for out of bounds [5..] and another for slicing [..10]. + &x[0..][..3]; + &x[1..][..5]; + + &x[0..].get(..3); // Ok, should not produce stderr. + &x[0..3]; // Ok, should not produce stderr. + + let y = &x; + &y[1..2]; + &y[0..=4]; + &y[..=4]; + + &y[..]; // Ok, should not produce stderr. + + let v = vec![0; 5]; + &v[10..100]; + &x[10..][..100]; // Two lint reports, one for [10..] and another for [..100]. + &v[10..]; + &v[..100]; + + &v[..]; // Ok, should not produce stderr. +} diff --git a/src/tools/clippy/tests/ui/indexing_slicing_slice.stderr b/src/tools/clippy/tests/ui/indexing_slicing_slice.stderr new file mode 100644 index 0000000000000..ec6c157ac1a26 --- /dev/null +++ b/src/tools/clippy/tests/ui/indexing_slicing_slice.stderr @@ -0,0 +1,137 @@ +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:12:6 + | +LL | &x[index..]; + | ^^^^^^^^^^ + | + = note: `-D clippy::indexing-slicing` implied by `-D warnings` + = help: Consider using `.get(n..)` or .get_mut(n..)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:13:6 + | +LL | &x[..index]; + | ^^^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:14:6 + | +LL | &x[index_from..index_to]; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: Consider using `.get(n..m)` or `.get_mut(n..m)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:15:6 + | +LL | &x[index_from..][..index_to]; // Two lint reports, one for [index_from..] and another for [..index_to]. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:15:6 + | +LL | &x[index_from..][..index_to]; // Two lint reports, one for [index_from..] and another for [..index_to]. + | ^^^^^^^^^^^^^^^ + | + = help: Consider using `.get(n..)` or .get_mut(n..)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:16:6 + | +LL | &x[5..][..10]; // Two lint reports, one for out of bounds [5..] and another for slicing [..10]. + | ^^^^^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: range is out of bounds + --> $DIR/indexing_slicing_slice.rs:16:8 + | +LL | &x[5..][..10]; // Two lint reports, one for out of bounds [5..] and another for slicing [..10]. + | ^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:17:6 + | +LL | &x[0..][..3]; + | ^^^^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:18:6 + | +LL | &x[1..][..5]; + | ^^^^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:24:6 + | +LL | &y[1..2]; + | ^^^^^^^ + | + = help: Consider using `.get(n..m)` or `.get_mut(n..m)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:25:6 + | +LL | &y[0..=4]; + | ^^^^^^^^ + | + = help: Consider using `.get(n..m)` or `.get_mut(n..m)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:26:6 + | +LL | &y[..=4]; + | ^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:31:6 + | +LL | &v[10..100]; + | ^^^^^^^^^^ + | + = help: Consider using `.get(n..m)` or `.get_mut(n..m)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:32:6 + | +LL | &x[10..][..100]; // Two lint reports, one for [10..] and another for [..100]. + | ^^^^^^^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: range is out of bounds + --> $DIR/indexing_slicing_slice.rs:32:8 + | +LL | &x[10..][..100]; // Two lint reports, one for [10..] and another for [..100]. + | ^^ + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:33:6 + | +LL | &v[10..]; + | ^^^^^^^ + | + = help: Consider using `.get(n..)` or .get_mut(n..)` instead + +error: slicing may panic. + --> $DIR/indexing_slicing_slice.rs:34:6 + | +LL | &v[..100]; + | ^^^^^^^^ + | + = help: Consider using `.get(..n)`or `.get_mut(..n)` instead + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/inefficient_to_string.fixed b/src/tools/clippy/tests/ui/inefficient_to_string.fixed new file mode 100644 index 0000000000000..c972b9419ef76 --- /dev/null +++ b/src/tools/clippy/tests/ui/inefficient_to_string.fixed @@ -0,0 +1,31 @@ +// run-rustfix +#![deny(clippy::inefficient_to_string)] + +use std::borrow::Cow; + +fn main() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let rrrstr: &&&str = &rrstr; + let _: String = rstr.to_string(); + let _: String = (*rrstr).to_string(); + let _: String = (**rrrstr).to_string(); + + let string: String = String::from("hello"); + let rstring: &String = &string; + let rrstring: &&String = &rstring; + let rrrstring: &&&String = &rrstring; + let _: String = string.to_string(); + let _: String = rstring.to_string(); + let _: String = (*rrstring).to_string(); + let _: String = (**rrrstring).to_string(); + + let cow: Cow<'_, str> = Cow::Borrowed("hello"); + let rcow: &Cow<'_, str> = &cow; + let rrcow: &&Cow<'_, str> = &rcow; + let rrrcow: &&&Cow<'_, str> = &rrcow; + let _: String = cow.to_string(); + let _: String = rcow.to_string(); + let _: String = (*rrcow).to_string(); + let _: String = (**rrrcow).to_string(); +} diff --git a/src/tools/clippy/tests/ui/inefficient_to_string.rs b/src/tools/clippy/tests/ui/inefficient_to_string.rs new file mode 100644 index 0000000000000..acdc55aa0d69d --- /dev/null +++ b/src/tools/clippy/tests/ui/inefficient_to_string.rs @@ -0,0 +1,31 @@ +// run-rustfix +#![deny(clippy::inefficient_to_string)] + +use std::borrow::Cow; + +fn main() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let rrrstr: &&&str = &rrstr; + let _: String = rstr.to_string(); + let _: String = rrstr.to_string(); + let _: String = rrrstr.to_string(); + + let string: String = String::from("hello"); + let rstring: &String = &string; + let rrstring: &&String = &rstring; + let rrrstring: &&&String = &rrstring; + let _: String = string.to_string(); + let _: String = rstring.to_string(); + let _: String = rrstring.to_string(); + let _: String = rrrstring.to_string(); + + let cow: Cow<'_, str> = Cow::Borrowed("hello"); + let rcow: &Cow<'_, str> = &cow; + let rrcow: &&Cow<'_, str> = &rcow; + let rrrcow: &&&Cow<'_, str> = &rrcow; + let _: String = cow.to_string(); + let _: String = rcow.to_string(); + let _: String = rrcow.to_string(); + let _: String = rrrcow.to_string(); +} diff --git a/src/tools/clippy/tests/ui/inefficient_to_string.stderr b/src/tools/clippy/tests/ui/inefficient_to_string.stderr new file mode 100644 index 0000000000000..4be46161e8b74 --- /dev/null +++ b/src/tools/clippy/tests/ui/inefficient_to_string.stderr @@ -0,0 +1,55 @@ +error: calling `to_string` on `&&str` + --> $DIR/inefficient_to_string.rs:11:21 + | +LL | let _: String = rrstr.to_string(); + | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstr).to_string()` + | +note: the lint level is defined here + --> $DIR/inefficient_to_string.rs:2:9 + | +LL | #![deny(clippy::inefficient_to_string)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: `&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString` + +error: calling `to_string` on `&&&str` + --> $DIR/inefficient_to_string.rs:12:21 + | +LL | let _: String = rrrstr.to_string(); + | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstr).to_string()` + | + = help: `&&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString` + +error: calling `to_string` on `&&std::string::String` + --> $DIR/inefficient_to_string.rs:20:21 + | +LL | let _: String = rrstring.to_string(); + | ^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstring).to_string()` + | + = help: `&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` + +error: calling `to_string` on `&&&std::string::String` + --> $DIR/inefficient_to_string.rs:21:21 + | +LL | let _: String = rrrstring.to_string(); + | ^^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstring).to_string()` + | + = help: `&&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` + +error: calling `to_string` on `&&std::borrow::Cow` + --> $DIR/inefficient_to_string.rs:29:21 + | +LL | let _: String = rrcow.to_string(); + | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrcow).to_string()` + | + = help: `&std::borrow::Cow` implements `ToString` through a slower blanket impl, but `std::borrow::Cow` has a fast specialization of `ToString` + +error: calling `to_string` on `&&&std::borrow::Cow` + --> $DIR/inefficient_to_string.rs:30:21 + | +LL | let _: String = rrrcow.to_string(); + | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrcow).to_string()` + | + = help: `&&std::borrow::Cow` implements `ToString` through a slower blanket impl, but `std::borrow::Cow` has a fast specialization of `ToString` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/infallible_destructuring_match.fixed b/src/tools/clippy/tests/ui/infallible_destructuring_match.fixed new file mode 100644 index 0000000000000..b8e40d995531a --- /dev/null +++ b/src/tools/clippy/tests/ui/infallible_destructuring_match.fixed @@ -0,0 +1,112 @@ +// run-rustfix +#![feature(exhaustive_patterns, never_type)] +#![allow(dead_code, unreachable_code, unused_variables)] +#![allow(clippy::let_and_return)] + +enum SingleVariantEnum { + Variant(i32), +} + +struct TupleStruct(i32); + +enum EmptyEnum {} + +macro_rules! match_enum { + ($param:expr) => { + let data = match $param { + SingleVariantEnum::Variant(i) => i, + }; + }; +} + +fn infallible_destructuring_match_enum() { + let wrapper = SingleVariantEnum::Variant(0); + + // This should lint! + let SingleVariantEnum::Variant(data) = wrapper; + + // This shouldn't (inside macro) + match_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + SingleVariantEnum::Variant(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + SingleVariantEnum::Variant(i) => -1, + }; + + let SingleVariantEnum::Variant(data) = wrapper; +} + +macro_rules! match_struct { + ($param:expr) => { + let data = match $param { + TupleStruct(i) => i, + }; + }; +} + +fn infallible_destructuring_match_struct() { + let wrapper = TupleStruct(0); + + // This should lint! + let TupleStruct(data) = wrapper; + + // This shouldn't (inside macro) + match_struct!(wrapper); + + // This shouldn't! + let data = match wrapper { + TupleStruct(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + TupleStruct(i) => -1, + }; + + let TupleStruct(data) = wrapper; +} + +macro_rules! match_never_enum { + ($param:expr) => { + let data = match $param { + Ok(i) => i, + }; + }; +} + +fn never_enum() { + let wrapper: Result = Ok(23); + + // This should lint! + let Ok(data) = wrapper; + + // This shouldn't (inside macro) + match_never_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + Ok(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + Ok(i) => -1, + }; + + let Ok(data) = wrapper; +} + +impl EmptyEnum { + fn match_on(&self) -> ! { + // The lint shouldn't pick this up, as `let` won't work here! + let data = match *self {}; + data + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/infallible_destructuring_match.rs b/src/tools/clippy/tests/ui/infallible_destructuring_match.rs new file mode 100644 index 0000000000000..106cd438b90e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/infallible_destructuring_match.rs @@ -0,0 +1,118 @@ +// run-rustfix +#![feature(exhaustive_patterns, never_type)] +#![allow(dead_code, unreachable_code, unused_variables)] +#![allow(clippy::let_and_return)] + +enum SingleVariantEnum { + Variant(i32), +} + +struct TupleStruct(i32); + +enum EmptyEnum {} + +macro_rules! match_enum { + ($param:expr) => { + let data = match $param { + SingleVariantEnum::Variant(i) => i, + }; + }; +} + +fn infallible_destructuring_match_enum() { + let wrapper = SingleVariantEnum::Variant(0); + + // This should lint! + let data = match wrapper { + SingleVariantEnum::Variant(i) => i, + }; + + // This shouldn't (inside macro) + match_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + SingleVariantEnum::Variant(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + SingleVariantEnum::Variant(i) => -1, + }; + + let SingleVariantEnum::Variant(data) = wrapper; +} + +macro_rules! match_struct { + ($param:expr) => { + let data = match $param { + TupleStruct(i) => i, + }; + }; +} + +fn infallible_destructuring_match_struct() { + let wrapper = TupleStruct(0); + + // This should lint! + let data = match wrapper { + TupleStruct(i) => i, + }; + + // This shouldn't (inside macro) + match_struct!(wrapper); + + // This shouldn't! + let data = match wrapper { + TupleStruct(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + TupleStruct(i) => -1, + }; + + let TupleStruct(data) = wrapper; +} + +macro_rules! match_never_enum { + ($param:expr) => { + let data = match $param { + Ok(i) => i, + }; + }; +} + +fn never_enum() { + let wrapper: Result = Ok(23); + + // This should lint! + let data = match wrapper { + Ok(i) => i, + }; + + // This shouldn't (inside macro) + match_never_enum!(wrapper); + + // This shouldn't! + let data = match wrapper { + Ok(_) => -1, + }; + + // Neither should this! + let data = match wrapper { + Ok(i) => -1, + }; + + let Ok(data) = wrapper; +} + +impl EmptyEnum { + fn match_on(&self) -> ! { + // The lint shouldn't pick this up, as `let` won't work here! + let data = match *self {}; + data + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/infallible_destructuring_match.stderr b/src/tools/clippy/tests/ui/infallible_destructuring_match.stderr new file mode 100644 index 0000000000000..1b78db42014a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/infallible_destructuring_match.stderr @@ -0,0 +1,28 @@ +error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let` + --> $DIR/infallible_destructuring_match.rs:26:5 + | +LL | / let data = match wrapper { +LL | | SingleVariantEnum::Variant(i) => i, +LL | | }; + | |______^ help: try this: `let SingleVariantEnum::Variant(data) = wrapper;` + | + = note: `-D clippy::infallible-destructuring-match` implied by `-D warnings` + +error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let` + --> $DIR/infallible_destructuring_match.rs:58:5 + | +LL | / let data = match wrapper { +LL | | TupleStruct(i) => i, +LL | | }; + | |______^ help: try this: `let TupleStruct(data) = wrapper;` + +error: you seem to be trying to use `match` to destructure a single infallible pattern. Consider using `let` + --> $DIR/infallible_destructuring_match.rs:90:5 + | +LL | / let data = match wrapper { +LL | | Ok(i) => i, +LL | | }; + | |______^ help: try this: `let Ok(data) = wrapper;` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/infinite_iter.rs b/src/tools/clippy/tests/ui/infinite_iter.rs new file mode 100644 index 0000000000000..1fe688977659d --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_iter.rs @@ -0,0 +1,69 @@ +use std::iter::repeat; +fn square_is_lower_64(x: &u32) -> bool { + x * x < 64 +} + +#[allow(clippy::maybe_infinite_iter)] +#[deny(clippy::infinite_iter)] +fn infinite_iters() { + repeat(0_u8).collect::>(); // infinite iter + (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter + (0..8_u64).chain(0..).max(); // infinite iter + (0_usize..) + .chain([0usize, 1, 2].iter().cloned()) + .skip_while(|x| *x != 42) + .min(); // infinite iter + (0..8_u32) + .rev() + .cycle() + .map(|x| x + 1_u32) + .for_each(|x| println!("{}", x)); // infinite iter + (0..3_u32).flat_map(|x| x..).sum::(); // infinite iter + (0_usize..).flat_map(|x| 0..x).product::(); // infinite iter + (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter + (0..42_u64).by_ref().last(); // not an infinite, because ranges are double-ended + (0..).next(); // iterator is not exhausted +} + +#[deny(clippy::maybe_infinite_iter)] +fn potential_infinite_iters() { + (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter + repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter + (1..) + .scan(0, |state, x| { + *state += x; + Some(*state) + }) + .min(); // maybe infinite iter + (0..).find(|x| *x == 24); // maybe infinite iter + (0..).position(|x| x == 24); // maybe infinite iter + (0..).any(|x| x == 24); // maybe infinite iter + (0..).all(|x| x == 24); // maybe infinite iter + + (0..).zip(0..42).take_while(|&(x, _)| x != 42).count(); // not infinite + repeat(42).take_while(|x| *x == 42).next(); // iterator is not exhausted +} + +fn main() { + infinite_iters(); + potential_infinite_iters(); +} + +mod finite_collect { + use std::collections::HashSet; + use std::iter::FromIterator; + + struct C; + impl FromIterator for C { + fn from_iter>(iter: I) -> Self { + C + } + } + + fn check_collect() { + let _: HashSet = (0..).collect(); // Infinite iter + + // Some data structures don't collect infinitely, such as `ArrayVec` + let _: C = (0..).collect(); + } +} diff --git a/src/tools/clippy/tests/ui/infinite_iter.stderr b/src/tools/clippy/tests/ui/infinite_iter.stderr new file mode 100644 index 0000000000000..5f5e7ac9f253a --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_iter.stderr @@ -0,0 +1,109 @@ +error: infinite iteration detected + --> $DIR/infinite_iter.rs:9:5 + | +LL | repeat(0_u8).collect::>(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/infinite_iter.rs:7:8 + | +LL | #[deny(clippy::infinite_iter)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:10:5 + | +LL | (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:11:5 + | +LL | (0..8_u64).chain(0..).max(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:16:5 + | +LL | / (0..8_u32) +LL | | .rev() +LL | | .cycle() +LL | | .map(|x| x + 1_u32) +LL | | .for_each(|x| println!("{}", x)); // infinite iter + | |________________________________________^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:22:5 + | +LL | (0_usize..).flat_map(|x| 0..x).product::(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:23:5 + | +LL | (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:30:5 + | +LL | (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/infinite_iter.rs:28:8 + | +LL | #[deny(clippy::maybe_infinite_iter)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:31:5 + | +LL | repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:32:5 + | +LL | / (1..) +LL | | .scan(0, |state, x| { +LL | | *state += x; +LL | | Some(*state) +LL | | }) +LL | | .min(); // maybe infinite iter + | |______________^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:38:5 + | +LL | (0..).find(|x| *x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:39:5 + | +LL | (0..).position(|x| x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:40:5 + | +LL | (0..).any(|x| x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: possible infinite iteration detected + --> $DIR/infinite_iter.rs:41:5 + | +LL | (0..).all(|x| x == 24); // maybe infinite iter + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: infinite iteration detected + --> $DIR/infinite_iter.rs:64:31 + | +LL | let _: HashSet = (0..).collect(); // Infinite iter + | ^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::infinite_iter)]` on by default + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/infinite_loop.rs b/src/tools/clippy/tests/ui/infinite_loop.rs new file mode 100644 index 0000000000000..72591f12baf85 --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_loop.rs @@ -0,0 +1,206 @@ +fn fn_val(i: i32) -> i32 { + unimplemented!() +} +fn fn_constref(i: &i32) -> i32 { + unimplemented!() +} +fn fn_mutref(i: &mut i32) { + unimplemented!() +} +fn fooi() -> i32 { + unimplemented!() +} +fn foob() -> bool { + unimplemented!() +} + +#[allow(clippy::many_single_char_names)] +fn immutable_condition() { + // Should warn when all vars mentioned are immutable + let y = 0; + while y < 10 { + println!("KO - y is immutable"); + } + + let x = 0; + while y < 10 && x < 3 { + let mut k = 1; + k += 2; + println!("KO - x and y immutable"); + } + + let cond = false; + while !cond { + println!("KO - cond immutable"); + } + + let mut i = 0; + while y < 10 && i < 3 { + i += 1; + println!("OK - i is mutable"); + } + + let mut mut_cond = false; + while !mut_cond || cond { + mut_cond = true; + println!("OK - mut_cond is mutable"); + } + + while fooi() < x { + println!("OK - Fn call results may vary"); + } + + while foob() { + println!("OK - Fn call results may vary"); + } + + let mut a = 0; + let mut c = move || { + while a < 5 { + a += 1; + println!("OK - a is mutable"); + } + }; + c(); + + let mut tup = (0, 0); + while tup.0 < 5 { + tup.0 += 1; + println!("OK - tup.0 gets mutated") + } +} + +fn unused_var() { + // Should warn when a (mutable) var is not used in while body + let (mut i, mut j) = (0, 0); + + while i < 3 { + j = 3; + println!("KO - i not mentioned"); + } + + while i < 3 && j > 0 { + println!("KO - i and j not mentioned"); + } + + while i < 3 { + let mut i = 5; + fn_mutref(&mut i); + println!("KO - shadowed"); + } + + while i < 3 && j > 0 { + i = 5; + println!("OK - i in cond and mentioned"); + } +} + +fn used_immutable() { + let mut i = 0; + + while i < 3 { + fn_constref(&i); + println!("KO - const reference"); + } + + while i < 3 { + fn_val(i); + println!("KO - passed by value"); + } + + while i < 3 { + println!("OK - passed by mutable reference"); + fn_mutref(&mut i) + } + + while i < 3 { + fn_mutref(&mut i); + println!("OK - passed by mutable reference"); + } +} + +const N: i32 = 5; +const B: bool = false; + +fn consts() { + while false { + println!("Constants are not linted"); + } + + while B { + println!("Constants are not linted"); + } + + while N > 0 { + println!("Constants are not linted"); + } +} + +use std::cell::Cell; + +fn maybe_i_mutate(i: &Cell) { + unimplemented!() +} + +fn internally_mutable() { + let b = Cell::new(true); + + while b.get() { + // b cannot be silently coerced to `bool` + maybe_i_mutate(&b); + println!("OK - Method call within condition"); + } +} + +struct Counter { + count: usize, +} + +impl Counter { + fn inc(&mut self) { + self.count += 1; + } + + fn inc_n(&mut self, n: usize) { + while self.count < n { + self.inc(); + } + println!("OK - self borrowed mutably"); + } + + fn print_n(&self, n: usize) { + while self.count < n { + println!("KO - {} is not mutated", self.count); + } + } +} + +fn while_loop_with_break_and_return() { + let y = 0; + while y < 10 { + if y == 0 { + break; + } + println!("KO - loop contains break"); + } + + while y < 10 { + if y == 0 { + return; + } + println!("KO - loop contains return"); + } +} + +fn main() { + immutable_condition(); + unused_var(); + used_immutable(); + internally_mutable(); + + let mut c = Counter { count: 0 }; + c.inc_n(5); + c.print_n(2); + + while_loop_with_break_and_return(); +} diff --git a/src/tools/clippy/tests/ui/infinite_loop.stderr b/src/tools/clippy/tests/ui/infinite_loop.stderr new file mode 100644 index 0000000000000..1fcb29eff18e4 --- /dev/null +++ b/src/tools/clippy/tests/ui/infinite_loop.stderr @@ -0,0 +1,95 @@ +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:21:11 + | +LL | while y < 10 { + | ^^^^^^ + | + = note: `#[deny(clippy::while_immutable_condition)]` on by default + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:26:11 + | +LL | while y < 10 && x < 3 { + | ^^^^^^^^^^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:33:11 + | +LL | while !cond { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:77:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:82:11 + | +LL | while i < 3 && j > 0 { + | ^^^^^^^^^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:86:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:101:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:106:11 + | +LL | while i < 3 { + | ^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:172:15 + | +LL | while self.count < n { + | ^^^^^^^^^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:180:11 + | +LL | while y < 10 { + | ^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + = note: this loop contains `return`s or `break`s + = help: rewrite it as `if cond { loop { } }` + +error: variables in the condition are not mutated in the loop body + --> $DIR/infinite_loop.rs:187:11 + | +LL | while y < 10 { + | ^^^^^^ + | + = note: this may lead to an infinite or to a never running loop + = note: this loop contains `return`s or `break`s + = help: rewrite it as `if cond { loop { } }` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/inherent_to_string.rs b/src/tools/clippy/tests/ui/inherent_to_string.rs new file mode 100644 index 0000000000000..e6cf337d1bb1b --- /dev/null +++ b/src/tools/clippy/tests/ui/inherent_to_string.rs @@ -0,0 +1,96 @@ +#![warn(clippy::inherent_to_string)] +#![deny(clippy::inherent_to_string_shadow_display)] +#![allow(clippy::many_single_char_names)] + +use std::fmt; + +trait FalsePositive { + fn to_string(&self) -> String; +} + +struct A; +struct B; +struct C; +struct D; +struct E; +struct F; + +impl A { + // Should be detected; emit warning + fn to_string(&self) -> String { + "A.to_string()".to_string() + } + + // Should not be detected as it does not match the function signature + fn to_str(&self) -> String { + "A.to_str()".to_string() + } +} + +// Should not be detected as it is a free function +fn to_string() -> String { + "free to_string()".to_string() +} + +impl B { + // Should not be detected, wrong return type + fn to_string(&self) -> i32 { + 42 + } +} + +impl C { + // Should be detected and emit error as C also implements Display + fn to_string(&self) -> String { + "C.to_string()".to_string() + } +} + +impl fmt::Display for C { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "impl Display for C") + } +} + +impl FalsePositive for D { + // Should not be detected, as it is a trait function + fn to_string(&self) -> String { + "impl FalsePositive for D".to_string() + } +} + +impl E { + // Should not be detected, as it is not bound to an instance + fn to_string() -> String { + "E::to_string()".to_string() + } +} + +impl F { + // Should not be detected, as it does not match the function signature + fn to_string(&self, _i: i32) -> String { + "F.to_string()".to_string() + } +} + +fn main() { + let a = A; + a.to_string(); + a.to_str(); + + to_string(); + + let b = B; + b.to_string(); + + let c = C; + C.to_string(); + + let d = D; + d.to_string(); + + E::to_string(); + + let f = F; + f.to_string(1); +} diff --git a/src/tools/clippy/tests/ui/inherent_to_string.stderr b/src/tools/clippy/tests/ui/inherent_to_string.stderr new file mode 100644 index 0000000000000..4f331f5bec9e6 --- /dev/null +++ b/src/tools/clippy/tests/ui/inherent_to_string.stderr @@ -0,0 +1,28 @@ +error: implementation of inherent method `to_string(&self) -> String` for type `A` + --> $DIR/inherent_to_string.rs:20:5 + | +LL | / fn to_string(&self) -> String { +LL | | "A.to_string()".to_string() +LL | | } + | |_____^ + | + = note: `-D clippy::inherent-to-string` implied by `-D warnings` + = help: implement trait `Display` for type `A` instead + +error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display` + --> $DIR/inherent_to_string.rs:44:5 + | +LL | / fn to_string(&self) -> String { +LL | | "C.to_string()".to_string() +LL | | } + | |_____^ + | +note: the lint level is defined here + --> $DIR/inherent_to_string.rs:2:9 + | +LL | #![deny(clippy::inherent_to_string_shadow_display)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: remove the inherent method from type `C` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/inline_fn_without_body.fixed b/src/tools/clippy/tests/ui/inline_fn_without_body.fixed new file mode 100644 index 0000000000000..fe21a71a42c26 --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_fn_without_body.fixed @@ -0,0 +1,17 @@ +// run-rustfix + +#![warn(clippy::inline_fn_without_body)] +#![allow(clippy::inline_always)] + +trait Foo { + fn default_inline(); + + fn always_inline(); + + fn never_inline(); + + #[inline] + fn has_body() {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/inline_fn_without_body.rs b/src/tools/clippy/tests/ui/inline_fn_without_body.rs new file mode 100644 index 0000000000000..5074698946650 --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_fn_without_body.rs @@ -0,0 +1,20 @@ +// run-rustfix + +#![warn(clippy::inline_fn_without_body)] +#![allow(clippy::inline_always)] + +trait Foo { + #[inline] + fn default_inline(); + + #[inline(always)] + fn always_inline(); + + #[inline(never)] + fn never_inline(); + + #[inline] + fn has_body() {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/inline_fn_without_body.stderr b/src/tools/clippy/tests/ui/inline_fn_without_body.stderr new file mode 100644 index 0000000000000..32d35e209b01b --- /dev/null +++ b/src/tools/clippy/tests/ui/inline_fn_without_body.stderr @@ -0,0 +1,28 @@ +error: use of `#[inline]` on trait method `default_inline` which has no body + --> $DIR/inline_fn_without_body.rs:7:5 + | +LL | #[inline] + | _____-^^^^^^^^ +LL | | fn default_inline(); + | |____- help: remove + | + = note: `-D clippy::inline-fn-without-body` implied by `-D warnings` + +error: use of `#[inline]` on trait method `always_inline` which has no body + --> $DIR/inline_fn_without_body.rs:10:5 + | +LL | #[inline(always)] + | _____-^^^^^^^^^^^^^^^^ +LL | | fn always_inline(); + | |____- help: remove + +error: use of `#[inline]` on trait method `never_inline` which has no body + --> $DIR/inline_fn_without_body.rs:13:5 + | +LL | #[inline(never)] + | _____-^^^^^^^^^^^^^^^ +LL | | fn never_inline(); + | |____- help: remove + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/int_plus_one.fixed b/src/tools/clippy/tests/ui/int_plus_one.fixed new file mode 100644 index 0000000000000..642830f24f58a --- /dev/null +++ b/src/tools/clippy/tests/ui/int_plus_one.fixed @@ -0,0 +1,17 @@ +// run-rustfix + +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +#[warn(clippy::int_plus_one)] +fn main() { + let x = 1i32; + let y = 0i32; + + let _ = x > y; + let _ = y < x; + + let _ = x > y; + let _ = y < x; + + let _ = x > y; // should be ok + let _ = y < x; // should be ok +} diff --git a/src/tools/clippy/tests/ui/int_plus_one.rs b/src/tools/clippy/tests/ui/int_plus_one.rs new file mode 100644 index 0000000000000..0755a0c79d280 --- /dev/null +++ b/src/tools/clippy/tests/ui/int_plus_one.rs @@ -0,0 +1,17 @@ +// run-rustfix + +#[allow(clippy::no_effect, clippy::unnecessary_operation)] +#[warn(clippy::int_plus_one)] +fn main() { + let x = 1i32; + let y = 0i32; + + let _ = x >= y + 1; + let _ = y + 1 <= x; + + let _ = x - 1 >= y; + let _ = y <= x - 1; + + let _ = x > y; // should be ok + let _ = y < x; // should be ok +} diff --git a/src/tools/clippy/tests/ui/int_plus_one.stderr b/src/tools/clippy/tests/ui/int_plus_one.stderr new file mode 100644 index 0000000000000..29a6914761c9f --- /dev/null +++ b/src/tools/clippy/tests/ui/int_plus_one.stderr @@ -0,0 +1,28 @@ +error: Unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:9:13 + | +LL | let _ = x >= y + 1; + | ^^^^^^^^^^ help: change it to: `x > y` + | + = note: `-D clippy::int-plus-one` implied by `-D warnings` + +error: Unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:10:13 + | +LL | let _ = y + 1 <= x; + | ^^^^^^^^^^ help: change it to: `y < x` + +error: Unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:12:13 + | +LL | let _ = x - 1 >= y; + | ^^^^^^^^^^ help: change it to: `x > y` + +error: Unnecessary `>= y + 1` or `x - 1 >=` + --> $DIR/int_plus_one.rs:13:13 + | +LL | let _ = y <= x - 1; + | ^^^^^^^^^^ help: change it to: `y < x` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/integer_arithmetic.rs b/src/tools/clippy/tests/ui/integer_arithmetic.rs new file mode 100644 index 0000000000000..7b1b64f390a5a --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_arithmetic.rs @@ -0,0 +1,99 @@ +#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::op_ref +)] + +#[rustfmt::skip] +fn main() { + let mut i = 1i32; + 1 + i; + i * 2; + 1 % + i / 2; // no error, this is part of the expression in the preceding line + i - 2 + 2 - i; + -i; + i >> 1; + i << 1; + + // no error, overflows are checked by `overflowing_literals` + -1; + -(-1); + + i & 1; // no wrapping + i | 1; + i ^ 1; + + i += 1; + i -= 1; + i *= 2; + i /= 2; + i %= 2; + i <<= 3; + i >>= 2; + + // no errors + i |= 1; + i &= 1; + i ^= i; + + // No errors for the following items because they are constant expressions + enum Foo { + Bar = -2, + } + struct Baz([i32; 1 + 1]); + union Qux { + field: [i32; 1 + 1], + } + type Alias = [i32; 1 + 1]; + + const FOO: i32 = -2; + static BAR: i32 = -2; + + let _: [i32; 1 + 1] = [0, 0]; + + let _: [i32; 1 + 1] = { + let a: [i32; 1 + 1] = [0, 0]; + a + }; + + trait Trait { + const ASSOC: i32 = 1 + 1; + } + + impl Trait for Foo { + const ASSOC: i32 = { + let _: [i32; 1 + 1]; + fn foo() {} + 1 + 1 + }; + } +} + +// warn on references as well! (#5328) +pub fn int_arith_ref() { + 3 + &1; + &3 + 1; + &3 + &1; +} + +pub fn foo(x: &i32) -> i32 { + let a = 5; + a + x +} + +pub fn bar(x: &i32, y: &i32) -> i32 { + x + y +} + +pub fn baz(x: i32, y: &i32) -> i32 { + x + y +} + +pub fn qux(x: i32, y: i32) -> i32 { + (&x + &y) +} diff --git a/src/tools/clippy/tests/ui/integer_arithmetic.stderr b/src/tools/clippy/tests/ui/integer_arithmetic.stderr new file mode 100644 index 0000000000000..83e8a9cde3ff1 --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_arithmetic.stderr @@ -0,0 +1,131 @@ +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:14:5 + | +LL | 1 + i; + | ^^^^^ + | + = note: `-D clippy::integer-arithmetic` implied by `-D warnings` + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:15:5 + | +LL | i * 2; + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:16:5 + | +LL | / 1 % +LL | | i / 2; // no error, this is part of the expression in the preceding line + | |_________^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:18:5 + | +LL | i - 2 + 2 - i; + | ^^^^^^^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:19:5 + | +LL | -i; + | ^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:20:5 + | +LL | i >> 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:21:5 + | +LL | i << 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:31:5 + | +LL | i += 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:32:5 + | +LL | i -= 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:33:5 + | +LL | i *= 2; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:34:5 + | +LL | i /= 2; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:35:5 + | +LL | i %= 2; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:36:5 + | +LL | i <<= 3; + | ^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:37:5 + | +LL | i >>= 2; + | ^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:79:5 + | +LL | 3 + &1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:80:5 + | +LL | &3 + 1; + | ^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:81:5 + | +LL | &3 + &1; + | ^^^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:86:5 + | +LL | a + x + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:90:5 + | +LL | x + y + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:94:5 + | +LL | x + y + | ^^^^^ + +error: integer arithmetic detected + --> $DIR/integer_arithmetic.rs:98:5 + | +LL | (&x + &y) + | ^^^^^^^^^ + +error: aborting due to 21 previous errors + diff --git a/src/tools/clippy/tests/ui/integer_division.rs b/src/tools/clippy/tests/ui/integer_division.rs new file mode 100644 index 0000000000000..800c75257524a --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_division.rs @@ -0,0 +1,9 @@ +#![warn(clippy::integer_division)] + +fn main() { + let two = 2; + let n = 1 / 2; + let o = 1 / two; + let p = two / 4; + let x = 1. / 2.0; +} diff --git a/src/tools/clippy/tests/ui/integer_division.stderr b/src/tools/clippy/tests/ui/integer_division.stderr new file mode 100644 index 0000000000000..72a232ef3d750 --- /dev/null +++ b/src/tools/clippy/tests/ui/integer_division.stderr @@ -0,0 +1,27 @@ +error: integer division + --> $DIR/integer_division.rs:5:13 + | +LL | let n = 1 / 2; + | ^^^^^ + | + = note: `-D clippy::integer-division` implied by `-D warnings` + = help: division of integers may cause loss of precision. consider using floats. + +error: integer division + --> $DIR/integer_division.rs:6:13 + | +LL | let o = 1 / two; + | ^^^^^^^ + | + = help: division of integers may cause loss of precision. consider using floats. + +error: integer division + --> $DIR/integer_division.rs:7:13 + | +LL | let p = two / 4; + | ^^^^^^^ + | + = help: division of integers may cause loss of precision. consider using floats. + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/into_iter_on_ref.fixed b/src/tools/clippy/tests/ui/into_iter_on_ref.fixed new file mode 100644 index 0000000000000..c30d23de3f869 --- /dev/null +++ b/src/tools/clippy/tests/ui/into_iter_on_ref.fixed @@ -0,0 +1,43 @@ +// run-rustfix +#![allow(clippy::useless_vec)] +#![warn(clippy::into_iter_on_ref)] + +struct X; +use std::collections::*; + +fn main() { + for _ in &[1, 2, 3] {} + for _ in vec![X, X] {} + for _ in &vec![X, X] {} + + let _ = vec![1, 2, 3].into_iter(); + let _ = (&vec![1, 2, 3]).iter(); //~ WARN equivalent to .iter() + let _ = vec![1, 2, 3].into_boxed_slice().iter(); //~ WARN equivalent to .iter() + let _ = std::rc::Rc::from(&[X][..]).iter(); //~ WARN equivalent to .iter() + let _ = std::sync::Arc::from(&[X][..]).iter(); //~ WARN equivalent to .iter() + + let _ = (&&&&&&&[1, 2, 3]).iter(); //~ ERROR equivalent to .iter() + let _ = (&&&&mut &&&[1, 2, 3]).iter(); //~ ERROR equivalent to .iter() + let _ = (&mut &mut &mut [1, 2, 3]).iter_mut(); //~ ERROR equivalent to .iter_mut() + + let _ = (&Some(4)).iter(); //~ WARN equivalent to .iter() + let _ = (&mut Some(5)).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&Ok::<_, i32>(6)).iter(); //~ WARN equivalent to .iter() + let _ = (&mut Err::(7)).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&Vec::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut Vec::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&BTreeMap::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut BTreeMap::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&VecDeque::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut VecDeque::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&LinkedList::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut LinkedList::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + let _ = (&HashMap::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&mut HashMap::::new()).iter_mut(); //~ WARN equivalent to .iter_mut() + + let _ = (&BTreeSet::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&BinaryHeap::::new()).iter(); //~ WARN equivalent to .iter() + let _ = (&HashSet::::new()).iter(); //~ WARN equivalent to .iter() + let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter() + let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter() +} diff --git a/src/tools/clippy/tests/ui/into_iter_on_ref.rs b/src/tools/clippy/tests/ui/into_iter_on_ref.rs new file mode 100644 index 0000000000000..94bc1689619a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/into_iter_on_ref.rs @@ -0,0 +1,43 @@ +// run-rustfix +#![allow(clippy::useless_vec)] +#![warn(clippy::into_iter_on_ref)] + +struct X; +use std::collections::*; + +fn main() { + for _ in &[1, 2, 3] {} + for _ in vec![X, X] {} + for _ in &vec![X, X] {} + + let _ = vec![1, 2, 3].into_iter(); + let _ = (&vec![1, 2, 3]).into_iter(); //~ WARN equivalent to .iter() + let _ = vec![1, 2, 3].into_boxed_slice().into_iter(); //~ WARN equivalent to .iter() + let _ = std::rc::Rc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + let _ = std::sync::Arc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + + let _ = (&&&&&&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + let _ = (&&&&mut &&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + let _ = (&mut &mut &mut [1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter_mut() + + let _ = (&Some(4)).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut Some(5)).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&Ok::<_, i32>(6)).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut Err::(7)).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&Vec::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut Vec::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + let _ = (&HashMap::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&mut HashMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + + let _ = (&BTreeSet::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&BinaryHeap::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = (&HashSet::::new()).into_iter(); //~ WARN equivalent to .iter() + let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() + let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() +} diff --git a/src/tools/clippy/tests/ui/into_iter_on_ref.stderr b/src/tools/clippy/tests/ui/into_iter_on_ref.stderr new file mode 100644 index 0000000000000..80e2d104f824f --- /dev/null +++ b/src/tools/clippy/tests/ui/into_iter_on_ref.stderr @@ -0,0 +1,160 @@ +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `Vec` + --> $DIR/into_iter_on_ref.rs:14:30 + | +LL | let _ = (&vec![1, 2, 3]).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + | + = note: `-D clippy::into-iter-on-ref` implied by `-D warnings` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `slice` + --> $DIR/into_iter_on_ref.rs:15:46 + | +LL | let _ = vec![1, 2, 3].into_boxed_slice().into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `slice` + --> $DIR/into_iter_on_ref.rs:16:41 + | +LL | let _ = std::rc::Rc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `slice` + --> $DIR/into_iter_on_ref.rs:17:44 + | +LL | let _ = std::sync::Arc::from(&[X][..]).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `array` + --> $DIR/into_iter_on_ref.rs:19:32 + | +LL | let _ = (&&&&&&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `array` + --> $DIR/into_iter_on_ref.rs:20:36 + | +LL | let _ = (&&&&mut &&&[1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `array` + --> $DIR/into_iter_on_ref.rs:21:40 + | +LL | let _ = (&mut &mut &mut [1, 2, 3]).into_iter(); //~ ERROR equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `Option` + --> $DIR/into_iter_on_ref.rs:23:24 + | +LL | let _ = (&Some(4)).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `Option` + --> $DIR/into_iter_on_ref.rs:24:28 + | +LL | let _ = (&mut Some(5)).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `Result` + --> $DIR/into_iter_on_ref.rs:25:32 + | +LL | let _ = (&Ok::<_, i32>(6)).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `Result` + --> $DIR/into_iter_on_ref.rs:26:37 + | +LL | let _ = (&mut Err::(7)).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `Vec` + --> $DIR/into_iter_on_ref.rs:27:34 + | +LL | let _ = (&Vec::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `Vec` + --> $DIR/into_iter_on_ref.rs:28:38 + | +LL | let _ = (&mut Vec::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `BTreeMap` + --> $DIR/into_iter_on_ref.rs:29:44 + | +LL | let _ = (&BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `BTreeMap` + --> $DIR/into_iter_on_ref.rs:30:48 + | +LL | let _ = (&mut BTreeMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `VecDeque` + --> $DIR/into_iter_on_ref.rs:31:39 + | +LL | let _ = (&VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `VecDeque` + --> $DIR/into_iter_on_ref.rs:32:43 + | +LL | let _ = (&mut VecDeque::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `LinkedList` + --> $DIR/into_iter_on_ref.rs:33:41 + | +LL | let _ = (&LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `LinkedList` + --> $DIR/into_iter_on_ref.rs:34:45 + | +LL | let _ = (&mut LinkedList::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `HashMap` + --> $DIR/into_iter_on_ref.rs:35:43 + | +LL | let _ = (&HashMap::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not move the `HashMap` + --> $DIR/into_iter_on_ref.rs:36:47 + | +LL | let _ = (&mut HashMap::::new()).into_iter(); //~ WARN equivalent to .iter_mut() + | ^^^^^^^^^ help: call directly: `iter_mut` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `BTreeSet` + --> $DIR/into_iter_on_ref.rs:38:39 + | +LL | let _ = (&BTreeSet::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `BinaryHeap` + --> $DIR/into_iter_on_ref.rs:39:41 + | +LL | let _ = (&BinaryHeap::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `HashSet` + --> $DIR/into_iter_on_ref.rs:40:38 + | +LL | let _ = (&HashSet::::new()).into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `Path` + --> $DIR/into_iter_on_ref.rs:41:43 + | +LL | let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `PathBuf` + --> $DIR/into_iter_on_ref.rs:42:47 + | +LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: aborting due to 26 previous errors + diff --git a/src/tools/clippy/tests/ui/invalid_upcast_comparisons.rs b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.rs new file mode 100644 index 0000000000000..697416dcee831 --- /dev/null +++ b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.rs @@ -0,0 +1,85 @@ +#![warn(clippy::invalid_upcast_comparisons)] +#![allow( + unused, + clippy::eq_op, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::cast_lossless +)] + +fn mk_value() -> T { + unimplemented!() +} + +fn main() { + let u32: u32 = mk_value(); + let u8: u8 = mk_value(); + let i32: i32 = mk_value(); + let i8: i8 = mk_value(); + + // always false, since no u8 can be > 300 + (u8 as u32) > 300; + (u8 as i32) > 300; + (u8 as u32) == 300; + (u8 as i32) == 300; + 300 < (u8 as u32); + 300 < (u8 as i32); + 300 == (u8 as u32); + 300 == (u8 as i32); + // inverted of the above + (u8 as u32) <= 300; + (u8 as i32) <= 300; + (u8 as u32) != 300; + (u8 as i32) != 300; + 300 >= (u8 as u32); + 300 >= (u8 as i32); + 300 != (u8 as u32); + 300 != (u8 as i32); + + // always false, since u8 -> i32 doesn't wrap + (u8 as i32) < 0; + -5 != (u8 as i32); + // inverted of the above + (u8 as i32) >= 0; + -5 == (u8 as i32); + + // always false, since no u8 can be 1337 + 1337 == (u8 as i32); + 1337 == (u8 as u32); + // inverted of the above + 1337 != (u8 as i32); + 1337 != (u8 as u32); + + // Those are Ok: + (u8 as u32) > 20; + 42 == (u8 as i32); + 42 != (u8 as i32); + 42 > (u8 as i32); + (u8 as i32) == 42; + (u8 as i32) != 42; + (u8 as i32) > 42; + (u8 as i32) < 42; + + (u8 as i8) == -1; + (u8 as i8) != -1; + (u8 as i32) > -1; + (u8 as i32) < -1; + (u32 as i32) < -5; + (u32 as i32) < 10; + + (i8 as u8) == 1; + (i8 as u8) != 1; + (i8 as u8) < 1; + (i8 as u8) > 1; + (i32 as u32) < 5; + (i32 as u32) < 10; + + -5 < (u32 as i32); + 0 <= (u32 as i32); + 0 < (u32 as i32); + + -5 > (u32 as i32); + -5 >= (u8 as i32); + + -5 == (u32 as i32); +} diff --git a/src/tools/clippy/tests/ui/invalid_upcast_comparisons.stderr b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.stderr new file mode 100644 index 0000000000000..03c3fb80aaabc --- /dev/null +++ b/src/tools/clippy/tests/ui/invalid_upcast_comparisons.stderr @@ -0,0 +1,166 @@ +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:21:5 + | +LL | (u8 as u32) > 300; + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-upcast-comparisons` implied by `-D warnings` + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:22:5 + | +LL | (u8 as i32) > 300; + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:23:5 + | +LL | (u8 as u32) == 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:24:5 + | +LL | (u8 as i32) == 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:25:5 + | +LL | 300 < (u8 as u32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:26:5 + | +LL | 300 < (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:27:5 + | +LL | 300 == (u8 as u32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:28:5 + | +LL | 300 == (u8 as i32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:30:5 + | +LL | (u8 as u32) <= 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:31:5 + | +LL | (u8 as i32) <= 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:32:5 + | +LL | (u8 as u32) != 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:33:5 + | +LL | (u8 as i32) != 300; + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:34:5 + | +LL | 300 >= (u8 as u32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:35:5 + | +LL | 300 >= (u8 as i32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:36:5 + | +LL | 300 != (u8 as u32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:37:5 + | +LL | 300 != (u8 as i32); + | ^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:40:5 + | +LL | (u8 as i32) < 0; + | ^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:41:5 + | +LL | -5 != (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:43:5 + | +LL | (u8 as i32) >= 0; + | ^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:44:5 + | +LL | -5 == (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:47:5 + | +LL | 1337 == (u8 as i32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:48:5 + | +LL | 1337 == (u8 as u32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:50:5 + | +LL | 1337 != (u8 as i32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:51:5 + | +LL | 1337 != (u8 as u32); + | ^^^^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always true + --> $DIR/invalid_upcast_comparisons.rs:65:5 + | +LL | (u8 as i32) > -1; + | ^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:66:5 + | +LL | (u8 as i32) < -1; + | ^^^^^^^^^^^^^^^^ + +error: because of the numeric bounds on `u8` prior to casting, this expression is always false + --> $DIR/invalid_upcast_comparisons.rs:82:5 + | +LL | -5 >= (u8 as i32); + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/issue-3145.rs b/src/tools/clippy/tests/ui/issue-3145.rs new file mode 100644 index 0000000000000..f497d5550af5f --- /dev/null +++ b/src/tools/clippy/tests/ui/issue-3145.rs @@ -0,0 +1,3 @@ +fn main() { + println!("{}" a); //~ERROR expected token: `,` +} diff --git a/src/tools/clippy/tests/ui/issue-3145.stderr b/src/tools/clippy/tests/ui/issue-3145.stderr new file mode 100644 index 0000000000000..cb0d95f5e2643 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue-3145.stderr @@ -0,0 +1,8 @@ +error: expected token: `,` + --> $DIR/issue-3145.rs:2:19 + | +LL | println!("{}" a); //~ERROR expected token: `,` + | ^ expected `,` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/issue-3746.rs b/src/tools/clippy/tests/ui/issue-3746.rs new file mode 100644 index 0000000000000..879d1d5d916e4 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue-3746.rs @@ -0,0 +1,22 @@ +// ignore-macos +// ignore-windows + +#![warn(clippy::empty_loop)] +#![feature(lang_items, link_args, start, libc)] +#![link_args = "-nostartfiles"] +#![no_std] + +use core::panic::PanicInfo; + +#[start] +fn main(argc: isize, argv: *const *const u8) -> isize { + loop {} +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} diff --git a/src/tools/clippy/tests/ui/issue_2356.rs b/src/tools/clippy/tests/ui/issue_2356.rs new file mode 100644 index 0000000000000..da580a1839a17 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_2356.rs @@ -0,0 +1,24 @@ +#![deny(clippy::while_let_on_iterator)] + +use std::iter::Iterator; + +struct Foo; + +impl Foo { + fn foo1>(mut it: I) { + while let Some(_) = it.next() { + println!("{:?}", it.size_hint()); + } + } + + fn foo2>(mut it: I) { + while let Some(e) = it.next() { + println!("{:?}", e); + } + } +} + +fn main() { + Foo::foo1(vec![].into_iter()); + Foo::foo2(vec![].into_iter()); +} diff --git a/src/tools/clippy/tests/ui/issue_2356.stderr b/src/tools/clippy/tests/ui/issue_2356.stderr new file mode 100644 index 0000000000000..51b872e21c085 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_2356.stderr @@ -0,0 +1,14 @@ +error: this loop could be written as a `for` loop + --> $DIR/issue_2356.rs:15:9 + | +LL | while let Some(e) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for e in it` + | +note: the lint level is defined here + --> $DIR/issue_2356.rs:1:9 + | +LL | #![deny(clippy::while_let_on_iterator)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/issue_4266.rs b/src/tools/clippy/tests/ui/issue_4266.rs new file mode 100644 index 0000000000000..8a9d5a3d1d569 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_4266.rs @@ -0,0 +1,37 @@ +// edition:2018 +#![allow(dead_code)] + +async fn sink1<'a>(_: &'a str) {} // lint +async fn sink1_elided(_: &str) {} // ok + +// lint +async fn one_to_one<'a>(s: &'a str) -> &'a str { + s +} + +// ok +async fn one_to_one_elided(s: &str) -> &str { + s +} + +// ok +async fn all_to_one<'a>(a: &'a str, _b: &'a str) -> &'a str { + a +} + +// async fn unrelated(_: &str, _: &str) {} // Not allowed in async fn + +// #3988 +struct Foo; +impl Foo { + // ok + pub async fn foo(&mut self) {} +} + +// rust-lang/rust#61115 +// ok +async fn print(s: &str) { + println!("{}", s); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/issue_4266.stderr b/src/tools/clippy/tests/ui/issue_4266.stderr new file mode 100644 index 0000000000000..0426508e622f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/issue_4266.stderr @@ -0,0 +1,16 @@ +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/issue_4266.rs:4:1 + | +LL | async fn sink1<'a>(_: &'a str) {} // lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::needless-lifetimes` implied by `-D warnings` + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/issue_4266.rs:8:1 + | +LL | async fn one_to_one<'a>(s: &'a str) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/item_after_statement.rs b/src/tools/clippy/tests/ui/item_after_statement.rs new file mode 100644 index 0000000000000..c17a7cbc8d905 --- /dev/null +++ b/src/tools/clippy/tests/ui/item_after_statement.rs @@ -0,0 +1,36 @@ +#![warn(clippy::items_after_statements)] + +fn ok() { + fn foo() { + println!("foo"); + } + foo(); +} + +fn last() { + foo(); + fn foo() { + println!("foo"); + } +} + +fn main() { + foo(); + fn foo() { + println!("foo"); + } + foo(); +} + +fn mac() { + let mut a = 5; + println!("{}", a); + // do not lint this, because it needs to be after `a` + macro_rules! b { + () => {{ + a = 6 + }}; + } + b!(); + println!("{}", a); +} diff --git a/src/tools/clippy/tests/ui/item_after_statement.stderr b/src/tools/clippy/tests/ui/item_after_statement.stderr new file mode 100644 index 0000000000000..f8f010b5e5c1f --- /dev/null +++ b/src/tools/clippy/tests/ui/item_after_statement.stderr @@ -0,0 +1,20 @@ +error: adding items after statements is confusing, since items exist from the start of the scope + --> $DIR/item_after_statement.rs:12:5 + | +LL | / fn foo() { +LL | | println!("foo"); +LL | | } + | |_____^ + | + = note: `-D clippy::items-after-statements` implied by `-D warnings` + +error: adding items after statements is confusing, since items exist from the start of the scope + --> $DIR/item_after_statement.rs:19:5 + | +LL | / fn foo() { +LL | | println!("foo"); +LL | | } + | |_____^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_cloned_collect.fixed b/src/tools/clippy/tests/ui/iter_cloned_collect.fixed new file mode 100644 index 0000000000000..2773227e26bca --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_cloned_collect.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +#![allow(unused)] + +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + let v = [1, 2, 3, 4, 5]; + let v2: Vec = v.to_vec(); + let v3: HashSet = v.iter().cloned().collect(); + let v4: VecDeque = v.iter().cloned().collect(); + + // Handle macro expansion in suggestion + let _: Vec = vec![1, 2, 3].to_vec(); + + // Issue #3704 + unsafe { + let _: Vec = std::ffi::CStr::from_ptr(std::ptr::null()) + .to_bytes().to_vec(); + } +} diff --git a/src/tools/clippy/tests/ui/iter_cloned_collect.rs b/src/tools/clippy/tests/ui/iter_cloned_collect.rs new file mode 100644 index 0000000000000..60a4eac23c79f --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_cloned_collect.rs @@ -0,0 +1,25 @@ +// run-rustfix + +#![allow(unused)] + +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + let v = [1, 2, 3, 4, 5]; + let v2: Vec = v.iter().cloned().collect(); + let v3: HashSet = v.iter().cloned().collect(); + let v4: VecDeque = v.iter().cloned().collect(); + + // Handle macro expansion in suggestion + let _: Vec = vec![1, 2, 3].iter().cloned().collect(); + + // Issue #3704 + unsafe { + let _: Vec = std::ffi::CStr::from_ptr(std::ptr::null()) + .to_bytes() + .iter() + .cloned() + .collect(); + } +} diff --git a/src/tools/clippy/tests/ui/iter_cloned_collect.stderr b/src/tools/clippy/tests/ui/iter_cloned_collect.stderr new file mode 100644 index 0000000000000..b90a1e6c91967 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_cloned_collect.stderr @@ -0,0 +1,26 @@ +error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable + --> $DIR/iter_cloned_collect.rs:10:27 + | +LL | let v2: Vec = v.iter().cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()` + | + = note: `-D clippy::iter-cloned-collect` implied by `-D warnings` + +error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable + --> $DIR/iter_cloned_collect.rs:15:38 + | +LL | let _: Vec = vec![1, 2, 3].iter().cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()` + +error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable + --> $DIR/iter_cloned_collect.rs:20:24 + | +LL | .to_bytes() + | ________________________^ +LL | | .iter() +LL | | .cloned() +LL | | .collect(); + | |______________________^ help: try: `.to_vec()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_nth.rs b/src/tools/clippy/tests/ui/iter_nth.rs new file mode 100644 index 0000000000000..9c21dd82ee45e --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth.rs @@ -0,0 +1,56 @@ +// aux-build:option_helpers.rs + +#![warn(clippy::iter_nth)] + +#[macro_use] +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; +use std::collections::VecDeque; + +/// Struct to generate false positives for things with `.iter()`. +#[derive(Copy, Clone)] +struct HasIter; + +impl HasIter { + fn iter(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } + + fn iter_mut(self) -> IteratorFalsePositives { + IteratorFalsePositives { foo: 0 } + } +} + +/// Checks implementation of `ITER_NTH` lint. +fn iter_nth() { + let mut some_vec = vec![0, 1, 2, 3]; + let mut boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let mut some_vec_deque: VecDeque<_> = some_vec.iter().cloned().collect(); + + { + // Make sure we lint `.iter()` for relevant types. + let bad_vec = some_vec.iter().nth(3); + let bad_slice = &some_vec[..].iter().nth(3); + let bad_boxed_slice = boxed_slice.iter().nth(3); + let bad_vec_deque = some_vec_deque.iter().nth(3); + } + + { + // Make sure we lint `.iter_mut()` for relevant types. + let bad_vec = some_vec.iter_mut().nth(3); + } + { + let bad_slice = &some_vec[..].iter_mut().nth(3); + } + { + let bad_vec_deque = some_vec_deque.iter_mut().nth(3); + } + + // Make sure we don't lint for non-relevant types. + let false_positive = HasIter; + let ok = false_positive.iter().nth(3); + let ok_mut = false_positive.iter_mut().nth(3); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/iter_nth.stderr b/src/tools/clippy/tests/ui/iter_nth.stderr new file mode 100644 index 0000000000000..d00b2fb672bb6 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth.stderr @@ -0,0 +1,59 @@ +error: called `.iter().nth()` on a Vec + --> $DIR/iter_nth.rs:33:23 + | +LL | let bad_vec = some_vec.iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iter-nth` implied by `-D warnings` + = help: calling `.get()` is both faster and more readable + +error: called `.iter().nth()` on a slice + --> $DIR/iter_nth.rs:34:26 + | +LL | let bad_slice = &some_vec[..].iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get()` is both faster and more readable + +error: called `.iter().nth()` on a slice + --> $DIR/iter_nth.rs:35:31 + | +LL | let bad_boxed_slice = boxed_slice.iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get()` is both faster and more readable + +error: called `.iter().nth()` on a VecDeque + --> $DIR/iter_nth.rs:36:29 + | +LL | let bad_vec_deque = some_vec_deque.iter().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get()` is both faster and more readable + +error: called `.iter_mut().nth()` on a Vec + --> $DIR/iter_nth.rs:41:23 + | +LL | let bad_vec = some_vec.iter_mut().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get_mut()` is both faster and more readable + +error: called `.iter_mut().nth()` on a slice + --> $DIR/iter_nth.rs:44:26 + | +LL | let bad_slice = &some_vec[..].iter_mut().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get_mut()` is both faster and more readable + +error: called `.iter_mut().nth()` on a VecDeque + --> $DIR/iter_nth.rs:47:29 + | +LL | let bad_vec_deque = some_vec_deque.iter_mut().nth(3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: calling `.get_mut()` is both faster and more readable + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_nth_zero.fixed b/src/tools/clippy/tests/ui/iter_nth_zero.fixed new file mode 100644 index 0000000000000..b54147c94d192 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth_zero.fixed @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::iter_nth_zero)] +use std::collections::HashSet; + +struct Foo {} + +impl Foo { + fn nth(&self, index: usize) -> usize { + index + 1 + } +} + +fn main() { + let f = Foo {}; + f.nth(0); // lint does not apply here + + let mut s = HashSet::new(); + s.insert(1); + let _x = s.iter().next(); + + let mut s2 = HashSet::new(); + s2.insert(2); + let mut iter = s2.iter(); + let _y = iter.next(); + + let mut s3 = HashSet::new(); + s3.insert(3); + let mut iter2 = s3.iter(); + let _unwrapped = iter2.next().unwrap(); +} diff --git a/src/tools/clippy/tests/ui/iter_nth_zero.rs b/src/tools/clippy/tests/ui/iter_nth_zero.rs new file mode 100644 index 0000000000000..b92c7d18adb4f --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth_zero.rs @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::iter_nth_zero)] +use std::collections::HashSet; + +struct Foo {} + +impl Foo { + fn nth(&self, index: usize) -> usize { + index + 1 + } +} + +fn main() { + let f = Foo {}; + f.nth(0); // lint does not apply here + + let mut s = HashSet::new(); + s.insert(1); + let _x = s.iter().nth(0); + + let mut s2 = HashSet::new(); + s2.insert(2); + let mut iter = s2.iter(); + let _y = iter.nth(0); + + let mut s3 = HashSet::new(); + s3.insert(3); + let mut iter2 = s3.iter(); + let _unwrapped = iter2.nth(0).unwrap(); +} diff --git a/src/tools/clippy/tests/ui/iter_nth_zero.stderr b/src/tools/clippy/tests/ui/iter_nth_zero.stderr new file mode 100644 index 0000000000000..2b20a4ceb4ab8 --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_nth_zero.stderr @@ -0,0 +1,22 @@ +error: called `.nth(0)` on a `std::iter::Iterator` + --> $DIR/iter_nth_zero.rs:20:14 + | +LL | let _x = s.iter().nth(0); + | ^^^^^^^^^^^^^^^ help: try calling: `s.iter().next()` + | + = note: `-D clippy::iter-nth-zero` implied by `-D warnings` + +error: called `.nth(0)` on a `std::iter::Iterator` + --> $DIR/iter_nth_zero.rs:25:14 + | +LL | let _y = iter.nth(0); + | ^^^^^^^^^^^ help: try calling: `iter.next()` + +error: called `.nth(0)` on a `std::iter::Iterator` + --> $DIR/iter_nth_zero.rs:30:22 + | +LL | let _unwrapped = iter2.nth(0).unwrap(); + | ^^^^^^^^^^^^ help: try calling: `iter2.next()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/iter_skip_next.rs b/src/tools/clippy/tests/ui/iter_skip_next.rs new file mode 100644 index 0000000000000..a65ca3bbb131b --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_next.rs @@ -0,0 +1,22 @@ +// aux-build:option_helpers.rs + +#![warn(clippy::iter_skip_next)] +#![allow(clippy::blacklisted_name)] + +extern crate option_helpers; + +use option_helpers::IteratorFalsePositives; + +/// Checks implementation of `ITER_SKIP_NEXT` lint +fn iter_skip_next() { + let mut some_vec = vec![0, 1, 2, 3]; + let _ = some_vec.iter().skip(42).next(); + let _ = some_vec.iter().cycle().skip(42).next(); + let _ = (1..10).skip(10).next(); + let _ = &some_vec[..].iter().skip(3).next(); + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.skip(42).next(); + let _ = foo.filter().skip(42).next(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/iter_skip_next.stderr b/src/tools/clippy/tests/ui/iter_skip_next.stderr new file mode 100644 index 0000000000000..5709f3355298b --- /dev/null +++ b/src/tools/clippy/tests/ui/iter_skip_next.stderr @@ -0,0 +1,35 @@ +error: called `skip(x).next()` on an iterator + --> $DIR/iter_skip_next.rs:13:13 + | +LL | let _ = some_vec.iter().skip(42).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iter-skip-next` implied by `-D warnings` + = help: this is more succinctly expressed by calling `nth(x)` + +error: called `skip(x).next()` on an iterator + --> $DIR/iter_skip_next.rs:14:13 + | +LL | let _ = some_vec.iter().cycle().skip(42).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: this is more succinctly expressed by calling `nth(x)` + +error: called `skip(x).next()` on an iterator + --> $DIR/iter_skip_next.rs:15:13 + | +LL | let _ = (1..10).skip(10).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: this is more succinctly expressed by calling `nth(x)` + +error: called `skip(x).next()` on an iterator + --> $DIR/iter_skip_next.rs:16:14 + | +LL | let _ = &some_vec[..].iter().skip(3).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: this is more succinctly expressed by calling `nth(x)` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/iterator_step_by_zero.rs b/src/tools/clippy/tests/ui/iterator_step_by_zero.rs new file mode 100644 index 0000000000000..13d1cfd428185 --- /dev/null +++ b/src/tools/clippy/tests/ui/iterator_step_by_zero.rs @@ -0,0 +1,28 @@ +#[warn(clippy::iterator_step_by_zero)] +fn main() { + let _ = vec!["A", "B", "B"].iter().step_by(0); + let _ = "XXX".chars().step_by(0); + let _ = (0..1).step_by(0); + + // No error, not an iterator. + let y = NotIterator; + y.step_by(0); + + // No warning for non-zero step + let _ = (0..1).step_by(1); + + let _ = (1..).step_by(0); + let _ = (1..=2).step_by(0); + + let x = 0..1; + let _ = x.step_by(0); + + // check const eval + let v1 = vec![1, 2, 3]; + let _ = v1.iter().step_by(2 / 3); +} + +struct NotIterator; +impl NotIterator { + fn step_by(&self, _: u32) {} +} diff --git a/src/tools/clippy/tests/ui/iterator_step_by_zero.stderr b/src/tools/clippy/tests/ui/iterator_step_by_zero.stderr new file mode 100644 index 0000000000000..c2c6803b3e6e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/iterator_step_by_zero.stderr @@ -0,0 +1,46 @@ +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:3:13 + | +LL | let _ = vec!["A", "B", "B"].iter().step_by(0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iterator-step-by-zero` implied by `-D warnings` + +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:4:13 + | +LL | let _ = "XXX".chars().step_by(0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:5:13 + | +LL | let _ = (0..1).step_by(0); + | ^^^^^^^^^^^^^^^^^ + +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:14:13 + | +LL | let _ = (1..).step_by(0); + | ^^^^^^^^^^^^^^^^ + +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:15:13 + | +LL | let _ = (1..=2).step_by(0); + | ^^^^^^^^^^^^^^^^^^ + +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:18:13 + | +LL | let _ = x.step_by(0); + | ^^^^^^^^^^^^ + +error: Iterator::step_by(0) will panic at runtime + --> $DIR/iterator_step_by_zero.rs:22:13 + | +LL | let _ = v1.iter().step_by(2 / 3); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/large_const_arrays.fixed b/src/tools/clippy/tests/ui/large_const_arrays.fixed new file mode 100644 index 0000000000000..c5af07c8a1728 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_const_arrays.fixed @@ -0,0 +1,37 @@ +// run-rustfix + +#![warn(clippy::large_const_arrays)] +#![allow(dead_code)] + +#[derive(Clone, Copy)] +pub struct S { + pub data: [u64; 32], +} + +// Should lint +pub(crate) static FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; +pub static FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +static FOO: [u32; 1_000_000] = [0u32; 1_000_000]; + +// Good +pub(crate) const G_FOO_PUB_CRATE: [u32; 1_000] = [0u32; 1_000]; +pub const G_FOO_PUB: [u32; 1_000] = [0u32; 1_000]; +const G_FOO: [u32; 1_000] = [0u32; 1_000]; + +fn main() { + // Should lint + pub static BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + static BAR: [u32; 1_000_000] = [0u32; 1_000_000]; + pub static BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + static BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + pub static BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; + static BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; + + // Good + pub const G_BAR_PUB: [u32; 1_000] = [0u32; 1_000]; + const G_BAR: [u32; 1_000] = [0u32; 1_000]; + pub const G_BAR_STRUCT_PUB: [S; 500] = [S { data: [0; 32] }; 500]; + const G_BAR_STRUCT: [S; 500] = [S { data: [0; 32] }; 500]; + pub const G_BAR_S_PUB: [Option<&str>; 200] = [Some("str"); 200]; + const G_BAR_S: [Option<&str>; 200] = [Some("str"); 200]; +} diff --git a/src/tools/clippy/tests/ui/large_const_arrays.rs b/src/tools/clippy/tests/ui/large_const_arrays.rs new file mode 100644 index 0000000000000..a160b9f8ad5b0 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_const_arrays.rs @@ -0,0 +1,37 @@ +// run-rustfix + +#![warn(clippy::large_const_arrays)] +#![allow(dead_code)] + +#[derive(Clone, Copy)] +pub struct S { + pub data: [u64; 32], +} + +// Should lint +pub(crate) const FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; +pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; + +// Good +pub(crate) const G_FOO_PUB_CRATE: [u32; 1_000] = [0u32; 1_000]; +pub const G_FOO_PUB: [u32; 1_000] = [0u32; 1_000]; +const G_FOO: [u32; 1_000] = [0u32; 1_000]; + +fn main() { + // Should lint + pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; + pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; + const BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; + + // Good + pub const G_BAR_PUB: [u32; 1_000] = [0u32; 1_000]; + const G_BAR: [u32; 1_000] = [0u32; 1_000]; + pub const G_BAR_STRUCT_PUB: [S; 500] = [S { data: [0; 32] }; 500]; + const G_BAR_STRUCT: [S; 500] = [S { data: [0; 32] }; 500]; + pub const G_BAR_S_PUB: [Option<&str>; 200] = [Some("str"); 200]; + const G_BAR_S: [Option<&str>; 200] = [Some("str"); 200]; +} diff --git a/src/tools/clippy/tests/ui/large_const_arrays.stderr b/src/tools/clippy/tests/ui/large_const_arrays.stderr new file mode 100644 index 0000000000000..3fb0acbca67de --- /dev/null +++ b/src/tools/clippy/tests/ui/large_const_arrays.stderr @@ -0,0 +1,76 @@ +error: large array defined as const + --> $DIR/large_const_arrays.rs:12:1 + | +LL | pub(crate) const FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; + | ^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + | + = note: `-D clippy::large-const-arrays` implied by `-D warnings` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:13:1 + | +LL | pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:14:1 + | +LL | const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:23:5 + | +LL | pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:24:5 + | +LL | const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:25:5 + | +LL | pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:26:5 + | +LL | const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:27:5 + | +LL | pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; + | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> $DIR/large_const_arrays.rs:28:5 + | +LL | const BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/large_digit_groups.fixed b/src/tools/clippy/tests/ui/large_digit_groups.fixed new file mode 100644 index 0000000000000..859fad2f54d9d --- /dev/null +++ b/src/tools/clippy/tests/ui/large_digit_groups.fixed @@ -0,0 +1,31 @@ +// run-rustfix +#![warn(clippy::large_digit_groups)] + +fn main() { + macro_rules! mac { + () => { + 0b1_10110_i64 + }; + } + + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x1_234_567, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = ( + 0b11_0110_i64, + 0xdead_beef_usize, + 123_456_f32, + 123_456.12_f32, + 123_456.123_45_f64, + 123_456.123_456_f64, + ); + // Ignore literals in macros + let _ = mac!(); +} diff --git a/src/tools/clippy/tests/ui/large_digit_groups.rs b/src/tools/clippy/tests/ui/large_digit_groups.rs new file mode 100644 index 0000000000000..ac116d5dbda15 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_digit_groups.rs @@ -0,0 +1,31 @@ +// run-rustfix +#![warn(clippy::large_digit_groups)] + +fn main() { + macro_rules! mac { + () => { + 0b1_10110_i64 + }; + } + + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x1_234_567, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = ( + 0b1_10110_i64, + 0xd_e_adbee_f_usize, + 1_23456_f32, + 1_23456.12_f32, + 1_23456.12345_f64, + 1_23456.12345_6_f64, + ); + // Ignore literals in macros + let _ = mac!(); +} diff --git a/src/tools/clippy/tests/ui/large_digit_groups.stderr b/src/tools/clippy/tests/ui/large_digit_groups.stderr new file mode 100644 index 0000000000000..b6d9672a78e21 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_digit_groups.stderr @@ -0,0 +1,42 @@ +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:22:9 + | +LL | 0b1_10110_i64, + | ^^^^^^^^^^^^^ help: consider: `0b11_0110_i64` + | + = note: `-D clippy::large-digit-groups` implied by `-D warnings` + +error: digits grouped inconsistently by underscores + --> $DIR/large_digit_groups.rs:23:9 + | +LL | 0xd_e_adbee_f_usize, + | ^^^^^^^^^^^^^^^^^^^ help: consider: `0xdead_beef_usize` + | + = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:24:9 + | +LL | 1_23456_f32, + | ^^^^^^^^^^^ help: consider: `123_456_f32` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:25:9 + | +LL | 1_23456.12_f32, + | ^^^^^^^^^^^^^^ help: consider: `123_456.12_f32` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:26:9 + | +LL | 1_23456.12345_f64, + | ^^^^^^^^^^^^^^^^^ help: consider: `123_456.123_45_f64` + +error: digit groups should be smaller + --> $DIR/large_digit_groups.rs:27:9 + | +LL | 1_23456.12345_6_f64, + | ^^^^^^^^^^^^^^^^^^^ help: consider: `123_456.123_456_f64` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/large_enum_variant.rs b/src/tools/clippy/tests/ui/large_enum_variant.rs new file mode 100644 index 0000000000000..852ef5fec0e7b --- /dev/null +++ b/src/tools/clippy/tests/ui/large_enum_variant.rs @@ -0,0 +1,54 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +#![warn(clippy::large_enum_variant)] + +enum LargeEnum { + A(i32), + B([i32; 8000]), +} + +enum GenericEnumOk { + A(i32), + B([T; 8000]), +} + +enum GenericEnum2 { + A(i32), + B([i32; 8000]), + C(T, [i32; 8000]), +} + +trait SomeTrait { + type Item; +} + +enum LargeEnumGeneric { + Var(A::Item), +} + +enum LargeEnum2 { + VariantOk(i32, u32), + ContainingLargeEnum(LargeEnum), +} +enum LargeEnum3 { + ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), + VoidVariant, + StructLikeLittle { x: i32, y: i32 }, +} + +enum LargeEnum4 { + VariantOk(i32, u32), + StructLikeLarge { x: [i32; 8000], y: i32 }, +} + +enum LargeEnum5 { + VariantOk(i32, u32), + StructLikeLarge2 { x: [i32; 8000] }, +} + +enum LargeEnumOk { + LargeA([i32; 8000]), + LargeB([i32; 8001]), +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/large_enum_variant.stderr b/src/tools/clippy/tests/ui/large_enum_variant.stderr new file mode 100644 index 0000000000000..8ce641a81f297 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_enum_variant.stderr @@ -0,0 +1,68 @@ +error: large size difference between variants + --> $DIR/large_enum_variant.rs:7:5 + | +LL | B([i32; 8000]), + | ^^^^^^^^^^^^^^ this variant is 32000 bytes + | + = note: `-D clippy::large-enum-variant` implied by `-D warnings` +note: and the second-largest variant is 4 bytes: + --> $DIR/large_enum_variant.rs:6:5 + | +LL | A(i32), + | ^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | B(Box<[i32; 8000]>), + | ^^^^^^^^^^^^^^^^ + +error: large size difference between variants + --> $DIR/large_enum_variant.rs:31:5 + | +LL | ContainingLargeEnum(LargeEnum), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32004 bytes + | +note: and the second-largest variant is 8 bytes: + --> $DIR/large_enum_variant.rs:30:5 + | +LL | VariantOk(i32, u32), + | ^^^^^^^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | ContainingLargeEnum(Box), + | ^^^^^^^^^^^^^^ + +error: large size difference between variants + --> $DIR/large_enum_variant.rs:41:5 + | +LL | StructLikeLarge { x: [i32; 8000], y: i32 }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32004 bytes + | +note: and the second-largest variant is 8 bytes: + --> $DIR/large_enum_variant.rs:40:5 + | +LL | VariantOk(i32, u32), + | ^^^^^^^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + --> $DIR/large_enum_variant.rs:41:5 + | +LL | StructLikeLarge { x: [i32; 8000], y: i32 }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: large size difference between variants + --> $DIR/large_enum_variant.rs:46:5 + | +LL | StructLikeLarge2 { x: [i32; 8000] }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32000 bytes + | +note: and the second-largest variant is 8 bytes: + --> $DIR/large_enum_variant.rs:45:5 + | +LL | VariantOk(i32, u32), + | ^^^^^^^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | StructLikeLarge2 { x: Box<[i32; 8000]> }, + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/large_stack_arrays.rs b/src/tools/clippy/tests/ui/large_stack_arrays.rs new file mode 100644 index 0000000000000..d9161bfcf1543 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_stack_arrays.rs @@ -0,0 +1,30 @@ +#![warn(clippy::large_stack_arrays)] +#![allow(clippy::large_enum_variant)] + +#[derive(Clone, Copy)] +struct S { + pub data: [u64; 32], +} + +#[derive(Clone, Copy)] +enum E { + S(S), + T(u32), +} + +fn main() { + let bad = ( + [0u32; 20_000_000], + [S { data: [0; 32] }; 5000], + [Some(""); 20_000_000], + [E::T(0); 5000], + ); + + let good = ( + [0u32; 1000], + [S { data: [0; 32] }; 1000], + [Some(""); 1000], + [E::T(0); 1000], + [(); 20_000_000], + ); +} diff --git a/src/tools/clippy/tests/ui/large_stack_arrays.stderr b/src/tools/clippy/tests/ui/large_stack_arrays.stderr new file mode 100644 index 0000000000000..58c0a77c1c841 --- /dev/null +++ b/src/tools/clippy/tests/ui/large_stack_arrays.stderr @@ -0,0 +1,35 @@ +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:17:9 + | +LL | [0u32; 20_000_000], + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::large-stack-arrays` implied by `-D warnings` + = help: consider allocating on the heap with `vec![0u32; 20_000_000].into_boxed_slice()` + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:18:9 + | +LL | [S { data: [0; 32] }; 5000], + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![S { data: [0; 32] }; 5000].into_boxed_slice()` + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:19:9 + | +LL | [Some(""); 20_000_000], + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![Some(""); 20_000_000].into_boxed_slice()` + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:20:9 + | +LL | [E::T(0); 5000], + | ^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with `vec![E::T(0); 5000].into_boxed_slice()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/len_without_is_empty.rs b/src/tools/clippy/tests/ui/len_without_is_empty.rs new file mode 100644 index 0000000000000..3ef29dd63880b --- /dev/null +++ b/src/tools/clippy/tests/ui/len_without_is_empty.rs @@ -0,0 +1,145 @@ +#![warn(clippy::len_without_is_empty)] +#![allow(dead_code, unused)] + +pub struct PubOne; + +impl PubOne { + pub fn len(self: &Self) -> isize { + 1 + } +} + +impl PubOne { + // A second impl for this struct -- the error span shouldn't mention this. + pub fn irrelevant(self: &Self) -> bool { + false + } +} + +// Identical to `PubOne`, but with an `allow` attribute on the impl complaining `len`. +pub struct PubAllowed; + +#[allow(clippy::len_without_is_empty)] +impl PubAllowed { + pub fn len(self: &Self) -> isize { + 1 + } +} + +// No `allow` attribute on this impl block, but that doesn't matter -- we only require one on the +// impl containing `len`. +impl PubAllowed { + pub fn irrelevant(self: &Self) -> bool { + false + } +} + +pub trait PubTraitsToo { + fn len(self: &Self) -> isize; +} + +impl PubTraitsToo for One { + fn len(self: &Self) -> isize { + 0 + } +} + +pub struct HasIsEmpty; + +impl HasIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +pub struct HasWrongIsEmpty; + +impl HasWrongIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + pub fn is_empty(self: &Self, x: u32) -> bool { + false + } +} + +struct NotPubOne; + +impl NotPubOne { + pub fn len(self: &Self) -> isize { + // No error; `len` is pub but `NotPubOne` is not exported anyway. + 1 + } +} + +struct One; + +impl One { + fn len(self: &Self) -> isize { + // No error; `len` is private; see issue #1085. + 1 + } +} + +trait TraitsToo { + fn len(self: &Self) -> isize; + // No error; `len` is private; see issue #1085. +} + +impl TraitsToo for One { + fn len(self: &Self) -> isize { + 0 + } +} + +struct HasPrivateIsEmpty; + +impl HasPrivateIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +struct Wither; + +pub trait WithIsEmpty { + fn len(self: &Self) -> isize; + fn is_empty(self: &Self) -> bool; +} + +impl WithIsEmpty for Wither { + fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +pub trait Empty { + fn is_empty(&self) -> bool; +} + +pub trait InheritingEmpty: Empty { + // Must not trigger `LEN_WITHOUT_IS_EMPTY`. + fn len(&self) -> isize; +} + +// This used to ICE. +pub trait Foo: Sized {} + +pub trait DependsOnFoo: Foo { + fn len(&mut self) -> usize; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/len_without_is_empty.stderr b/src/tools/clippy/tests/ui/len_without_is_empty.stderr new file mode 100644 index 0000000000000..4493b17a4b4e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_without_is_empty.stderr @@ -0,0 +1,54 @@ +error: item `PubOne` has a public `len` method but no corresponding `is_empty` method + --> $DIR/len_without_is_empty.rs:6:1 + | +LL | / impl PubOne { +LL | | pub fn len(self: &Self) -> isize { +LL | | 1 +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::len-without-is-empty` implied by `-D warnings` + +error: trait `PubTraitsToo` has a `len` method but no (possibly inherited) `is_empty` method + --> $DIR/len_without_is_empty.rs:37:1 + | +LL | / pub trait PubTraitsToo { +LL | | fn len(self: &Self) -> isize; +LL | | } + | |_^ + +error: item `HasIsEmpty` has a public `len` method but a private `is_empty` method + --> $DIR/len_without_is_empty.rs:49:1 + | +LL | / impl HasIsEmpty { +LL | | pub fn len(self: &Self) -> isize { +LL | | 1 +LL | | } +... | +LL | | } +LL | | } + | |_^ + +error: item `HasWrongIsEmpty` has a public `len` method but no corresponding `is_empty` method + --> $DIR/len_without_is_empty.rs:61:1 + | +LL | / impl HasWrongIsEmpty { +LL | | pub fn len(self: &Self) -> isize { +LL | | 1 +LL | | } +... | +LL | | } +LL | | } + | |_^ + +error: trait `DependsOnFoo` has a `len` method but no (possibly inherited) `is_empty` method + --> $DIR/len_without_is_empty.rs:141:1 + | +LL | / pub trait DependsOnFoo: Foo { +LL | | fn len(&mut self) -> usize; +LL | | } + | |_^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/len_zero.fixed b/src/tools/clippy/tests/ui/len_zero.fixed new file mode 100644 index 0000000000000..624e5ef8fcf13 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero.fixed @@ -0,0 +1,143 @@ +// run-rustfix + +#![warn(clippy::len_zero)] +#![allow(dead_code, unused, clippy::len_without_is_empty)] + +pub struct One; +struct Wither; + +trait TraitsToo { + fn len(self: &Self) -> isize; + // No error; `len` is private; see issue #1085. +} + +impl TraitsToo for One { + fn len(self: &Self) -> isize { + 0 + } +} + +pub struct HasIsEmpty; + +impl HasIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +pub struct HasWrongIsEmpty; + +impl HasWrongIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + pub fn is_empty(self: &Self, x: u32) -> bool { + false + } +} + +pub trait WithIsEmpty { + fn len(self: &Self) -> isize; + fn is_empty(self: &Self) -> bool; +} + +impl WithIsEmpty for Wither { + fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +fn main() { + let x = [1, 2]; + if x.is_empty() { + println!("This should not happen!"); + } + + if "".is_empty() {} + + let y = One; + if y.len() == 0 { + // No error; `One` does not have `.is_empty()`. + println!("This should not happen either!"); + } + + let z: &dyn TraitsToo = &y; + if z.len() > 0 { + // No error; `TraitsToo` has no `.is_empty()` method. + println!("Nor should this!"); + } + + let has_is_empty = HasIsEmpty; + if has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if has_is_empty.len() > 1 { + // No error. + println!("This can happen."); + } + if has_is_empty.len() <= 1 { + // No error. + println!("This can happen."); + } + if has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if !has_is_empty.is_empty() { + println!("Or this!"); + } + if has_is_empty.is_empty() { + println!("Or this!"); + } + if 1 < has_is_empty.len() { + // No error. + println!("This can happen."); + } + if 1 >= has_is_empty.len() { + // No error. + println!("This can happen."); + } + assert!(!has_is_empty.is_empty()); + + let with_is_empty: &dyn WithIsEmpty = &Wither; + if with_is_empty.is_empty() { + println!("Or this!"); + } + assert!(!with_is_empty.is_empty()); + + let has_wrong_is_empty = HasWrongIsEmpty; + if has_wrong_is_empty.len() == 0 { + // No error; `HasWrongIsEmpty` does not have `.is_empty()`. + println!("Or this!"); + } +} + +fn test_slice(b: &[u8]) { + if !b.is_empty() {} +} diff --git a/src/tools/clippy/tests/ui/len_zero.rs b/src/tools/clippy/tests/ui/len_zero.rs new file mode 100644 index 0000000000000..7fba971cfd887 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero.rs @@ -0,0 +1,143 @@ +// run-rustfix + +#![warn(clippy::len_zero)] +#![allow(dead_code, unused, clippy::len_without_is_empty)] + +pub struct One; +struct Wither; + +trait TraitsToo { + fn len(self: &Self) -> isize; + // No error; `len` is private; see issue #1085. +} + +impl TraitsToo for One { + fn len(self: &Self) -> isize { + 0 + } +} + +pub struct HasIsEmpty; + +impl HasIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +pub struct HasWrongIsEmpty; + +impl HasWrongIsEmpty { + pub fn len(self: &Self) -> isize { + 1 + } + + pub fn is_empty(self: &Self, x: u32) -> bool { + false + } +} + +pub trait WithIsEmpty { + fn len(self: &Self) -> isize; + fn is_empty(self: &Self) -> bool; +} + +impl WithIsEmpty for Wither { + fn len(self: &Self) -> isize { + 1 + } + + fn is_empty(self: &Self) -> bool { + false + } +} + +fn main() { + let x = [1, 2]; + if x.len() == 0 { + println!("This should not happen!"); + } + + if "".len() == 0 {} + + let y = One; + if y.len() == 0 { + // No error; `One` does not have `.is_empty()`. + println!("This should not happen either!"); + } + + let z: &dyn TraitsToo = &y; + if z.len() > 0 { + // No error; `TraitsToo` has no `.is_empty()` method. + println!("Nor should this!"); + } + + let has_is_empty = HasIsEmpty; + if has_is_empty.len() == 0 { + println!("Or this!"); + } + if has_is_empty.len() != 0 { + println!("Or this!"); + } + if has_is_empty.len() > 0 { + println!("Or this!"); + } + if has_is_empty.len() < 1 { + println!("Or this!"); + } + if has_is_empty.len() >= 1 { + println!("Or this!"); + } + if has_is_empty.len() > 1 { + // No error. + println!("This can happen."); + } + if has_is_empty.len() <= 1 { + // No error. + println!("This can happen."); + } + if 0 == has_is_empty.len() { + println!("Or this!"); + } + if 0 != has_is_empty.len() { + println!("Or this!"); + } + if 0 < has_is_empty.len() { + println!("Or this!"); + } + if 1 <= has_is_empty.len() { + println!("Or this!"); + } + if 1 > has_is_empty.len() { + println!("Or this!"); + } + if 1 < has_is_empty.len() { + // No error. + println!("This can happen."); + } + if 1 >= has_is_empty.len() { + // No error. + println!("This can happen."); + } + assert!(!has_is_empty.is_empty()); + + let with_is_empty: &dyn WithIsEmpty = &Wither; + if with_is_empty.len() == 0 { + println!("Or this!"); + } + assert!(!with_is_empty.is_empty()); + + let has_wrong_is_empty = HasWrongIsEmpty; + if has_wrong_is_empty.len() == 0 { + // No error; `HasWrongIsEmpty` does not have `.is_empty()`. + println!("Or this!"); + } +} + +fn test_slice(b: &[u8]) { + if b.len() != 0 {} +} diff --git a/src/tools/clippy/tests/ui/len_zero.stderr b/src/tools/clippy/tests/ui/len_zero.stderr new file mode 100644 index 0000000000000..6c71f1beeac67 --- /dev/null +++ b/src/tools/clippy/tests/ui/len_zero.stderr @@ -0,0 +1,88 @@ +error: length comparison to zero + --> $DIR/len_zero.rs:61:8 + | +LL | if x.len() == 0 { + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `x.is_empty()` + | + = note: `-D clippy::len-zero` implied by `-D warnings` + +error: length comparison to zero + --> $DIR/len_zero.rs:65:8 + | +LL | if "".len() == 0 {} + | ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `"".is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:80:8 + | +LL | if has_is_empty.len() == 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:83:8 + | +LL | if has_is_empty.len() != 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:86:8 + | +LL | if has_is_empty.len() > 0 { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:89:8 + | +LL | if has_is_empty.len() < 1 { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:92:8 + | +LL | if has_is_empty.len() >= 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:103:8 + | +LL | if 0 == has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:106:8 + | +LL | if 0 != has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:109:8 + | +LL | if 0 < has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:112:8 + | +LL | if 1 <= has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` + +error: length comparison to one + --> $DIR/len_zero.rs:115:8 + | +LL | if 1 > has_is_empty.len() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:129:8 + | +LL | if with_is_empty.len() == 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `with_is_empty.is_empty()` + +error: length comparison to zero + --> $DIR/len_zero.rs:142:8 + | +LL | if b.len() != 0 {} + | ^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!b.is_empty()` + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/let_if_seq.rs b/src/tools/clippy/tests/ui/let_if_seq.rs new file mode 100644 index 0000000000000..802beeb4be6b1 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_if_seq.rs @@ -0,0 +1,119 @@ +#![allow( + unused_variables, + unused_assignments, + clippy::similar_names, + clippy::blacklisted_name +)] +#![warn(clippy::useless_let_if_seq)] + +fn f() -> bool { + true +} +fn g(x: i32) -> i32 { + x + 1 +} + +fn issue985() -> i32 { + let mut x = 42; + if f() { + x = g(x); + } + + x +} + +fn issue985_alt() -> i32 { + let mut x = 42; + if f() { + f(); + } else { + x = g(x); + } + + x +} + +fn issue975() -> String { + let mut udn = "dummy".to_string(); + if udn.starts_with("uuid:") { + udn = String::from(&udn[5..]); + } + udn +} + +fn early_return() -> u8 { + // FIXME: we could extend the lint to include such cases: + let foo; + + if f() { + return 42; + } else { + foo = 0; + } + + foo +} + +fn main() { + early_return(); + issue975(); + issue985(); + issue985_alt(); + + let mut foo = 0; + if f() { + foo = 42; + } + + let mut bar = 0; + if f() { + f(); + bar = 42; + } else { + f(); + } + + let quz; + if f() { + quz = 42; + } else { + quz = 0; + } + + // `toto` is used several times + let mut toto; + if f() { + toto = 42; + } else { + for i in &[1, 2] { + toto = *i; + } + + toto = 2; + } + + // found in libcore, the inner if is not a statement but the block's expr + let mut ch = b'x'; + if f() { + ch = b'*'; + if f() { + ch = b'?'; + } + } + + // baz needs to be mut + let mut baz = 0; + if f() { + baz = 42; + } + + baz = 1337; + + // issue 3043 - types with interior mutability should not trigger this lint + use std::cell::Cell; + let mut val = Cell::new(1); + if true { + val = Cell::new(2); + } + println!("{}", val.get()); +} diff --git a/src/tools/clippy/tests/ui/let_if_seq.stderr b/src/tools/clippy/tests/ui/let_if_seq.stderr new file mode 100644 index 0000000000000..c53a63a541bc9 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_if_seq.stderr @@ -0,0 +1,50 @@ +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:63:5 + | +LL | / let mut foo = 0; +LL | | if f() { +LL | | foo = 42; +LL | | } + | |_____^ help: it is more idiomatic to write: `let foo = if f() { 42 } else { 0 };` + | + = note: `-D clippy::useless-let-if-seq` implied by `-D warnings` + = note: you might not need `mut` at all + +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:68:5 + | +LL | / let mut bar = 0; +LL | | if f() { +LL | | f(); +LL | | bar = 42; +LL | | } else { +LL | | f(); +LL | | } + | |_____^ help: it is more idiomatic to write: `let bar = if f() { ..; 42 } else { ..; 0 };` + | + = note: you might not need `mut` at all + +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:76:5 + | +LL | / let quz; +LL | | if f() { +LL | | quz = 42; +LL | | } else { +LL | | quz = 0; +LL | | } + | |_____^ help: it is more idiomatic to write: `let quz = if f() { 42 } else { 0 };` + +error: `if _ { .. } else { .. }` is an expression + --> $DIR/let_if_seq.rs:105:5 + | +LL | / let mut baz = 0; +LL | | if f() { +LL | | baz = 42; +LL | | } + | |_____^ help: it is more idiomatic to write: `let baz = if f() { 42 } else { 0 };` + | + = note: you might not need `mut` at all + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/let_return.rs b/src/tools/clippy/tests/ui/let_return.rs new file mode 100644 index 0000000000000..23645d48fe799 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_return.rs @@ -0,0 +1,70 @@ +#![allow(unused)] +#![warn(clippy::let_and_return)] + +fn test() -> i32 { + let _y = 0; // no warning + let x = 5; + x +} + +fn test_inner() -> i32 { + if true { + let x = 5; + x + } else { + 0 + } +} + +fn test_nowarn_1() -> i32 { + let mut x = 5; + x += 1; + x +} + +fn test_nowarn_2() -> i32 { + let x = 5; + x + 1 +} + +fn test_nowarn_3() -> (i32, i32) { + // this should technically warn, but we do not compare complex patterns + let (x, y) = (5, 9); + (x, y) +} + +fn test_nowarn_4() -> i32 { + // this should technically warn, but not b/c of clippy::let_and_return, but b/c of useless type + let x: i32 = 5; + x +} + +fn test_nowarn_5(x: i16) -> u16 { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let x = x as u16; + x +} + +// False positive example +trait Decode { + fn decode(d: D) -> Result + where + Self: Sized; +} + +macro_rules! tuple_encode { + ($($x:ident),*) => ( + impl<$($x: Decode),*> Decode for ($($x),*) { + #[inline] + #[allow(non_snake_case)] + fn decode(mut d: D) -> Result { + // Shouldn't trigger lint + Ok(($({let $x = Decode::decode(&mut d)?; $x }),*)) + } + } + ); +} + +tuple_encode!(T0, T1, T2, T3, T4, T5, T6, T7); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/let_return.stderr b/src/tools/clippy/tests/ui/let_return.stderr new file mode 100644 index 0000000000000..128a22c86e360 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_return.stderr @@ -0,0 +1,31 @@ +error: returning the result of a `let` binding from a block + --> $DIR/let_return.rs:7:5 + | +LL | let x = 5; + | ---------- unnecessary `let` binding +LL | x + | ^ + | + = note: `-D clippy::let-and-return` implied by `-D warnings` +help: return the expression directly + | +LL | +LL | 5 + | + +error: returning the result of a `let` binding from a block + --> $DIR/let_return.rs:13:9 + | +LL | let x = 5; + | ---------- unnecessary `let` binding +LL | x + | ^ + | +help: return the expression directly + | +LL | +LL | 5 + | + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/let_underscore_lock.rs b/src/tools/clippy/tests/ui/let_underscore_lock.rs new file mode 100644 index 0000000000000..88fb216a74329 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_lock.rs @@ -0,0 +1,13 @@ +#![warn(clippy::let_underscore_lock)] + +fn main() { + let m = std::sync::Mutex::new(()); + let rw = std::sync::RwLock::new(()); + + let _ = m.lock(); + let _ = rw.read(); + let _ = rw.write(); + let _ = m.try_lock(); + let _ = rw.try_read(); + let _ = rw.try_write(); +} diff --git a/src/tools/clippy/tests/ui/let_underscore_lock.stderr b/src/tools/clippy/tests/ui/let_underscore_lock.stderr new file mode 100644 index 0000000000000..5d5f6059ef13e --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_lock.stderr @@ -0,0 +1,51 @@ +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:7:5 + | +LL | let _ = m.lock(); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::let-underscore-lock` implied by `-D warnings` + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:8:5 + | +LL | let _ = rw.read(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:9:5 + | +LL | let _ = rw.write(); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:10:5 + | +LL | let _ = m.try_lock(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:11:5 + | +LL | let _ = rw.try_read(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:12:5 + | +LL | let _ = rw.try_write(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/let_underscore_must_use.rs b/src/tools/clippy/tests/ui/let_underscore_must_use.rs new file mode 100644 index 0000000000000..27dda606067aa --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_must_use.rs @@ -0,0 +1,94 @@ +#![warn(clippy::let_underscore_must_use)] + +// Debug implementations can fire this lint, +// so we shouldn't lint external macros +#[derive(Debug)] +struct Foo { + field: i32, +} + +#[must_use] +fn f() -> u32 { + 0 +} + +fn g() -> Result { + Ok(0) +} + +#[must_use] +fn l(x: T) -> T { + x +} + +fn h() -> u32 { + 0 +} + +struct S {} + +impl S { + #[must_use] + pub fn f(&self) -> u32 { + 0 + } + + pub fn g(&self) -> Result { + Ok(0) + } + + fn k(&self) -> u32 { + 0 + } + + #[must_use] + fn h() -> u32 { + 0 + } + + fn p() -> Result { + Ok(0) + } +} + +trait Trait { + #[must_use] + fn a() -> u32; +} + +impl Trait for S { + fn a() -> u32 { + 0 + } +} + +fn main() { + let _ = f(); + let _ = g(); + let _ = h(); + let _ = l(0_u32); + + let s = S {}; + + let _ = s.f(); + let _ = s.g(); + let _ = s.k(); + + let _ = S::h(); + let _ = S::p(); + + let _ = S::a(); + + let _ = if true { Ok(()) } else { Err(()) }; + + let a = Result::<(), ()>::Ok(()); + + let _ = a.is_ok(); + + let _ = a.map(|_| ()); + + let _ = a; + + #[allow(clippy::let_underscore_must_use)] + let _ = a; +} diff --git a/src/tools/clippy/tests/ui/let_underscore_must_use.stderr b/src/tools/clippy/tests/ui/let_underscore_must_use.stderr new file mode 100644 index 0000000000000..447f2419e3bdb --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_must_use.stderr @@ -0,0 +1,99 @@ +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:66:5 + | +LL | let _ = f(); + | ^^^^^^^^^^^^ + | + = note: `-D clippy::let-underscore-must-use` implied by `-D warnings` + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:67:5 + | +LL | let _ = g(); + | ^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:69:5 + | +LL | let _ = l(0_u32); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:73:5 + | +LL | let _ = s.f(); + | ^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:74:5 + | +LL | let _ = s.g(); + | ^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:77:5 + | +LL | let _ = S::h(); + | ^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:78:5 + | +LL | let _ = S::p(); + | ^^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:80:5 + | +LL | let _ = S::a(); + | ^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:82:5 + | +LL | let _ = if true { Ok(()) } else { Err(()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on a result of a `#[must_use]` function + --> $DIR/let_underscore_must_use.rs:86:5 + | +LL | let _ = a.is_ok(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using function result + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:88:5 + | +LL | let _ = a.map(|_| ()); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: non-binding let on an expression with `#[must_use]` type + --> $DIR/let_underscore_must_use.rs:90:5 + | +LL | let _ = a; + | ^^^^^^^^^^ + | + = help: consider explicitly using expression value + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/let_unit.fixed b/src/tools/clippy/tests/ui/let_unit.fixed new file mode 100644 index 0000000000000..f398edc23cb5e --- /dev/null +++ b/src/tools/clippy/tests/ui/let_unit.fixed @@ -0,0 +1,63 @@ +// run-rustfix + +#![warn(clippy::let_unit_value)] +#![allow(clippy::no_effect)] +#![allow(unused_variables)] + +macro_rules! let_and_return { + ($n:expr) => {{ + let ret = $n; + }}; +} + +fn main() { + println!("x"); + let _y = 1; // this is fine + let _z = ((), 1); // this as well + if true { + (); + } + + consume_units_with_for_loop(); // should be fine as well + + multiline_sugg(); + + let_and_return!(()) // should be fine +} + +// Related to issue #1964 +fn consume_units_with_for_loop() { + // `for_let_unit` lint should not be triggered by consuming them using for loop. + let v = vec![(), (), ()]; + let mut count = 0; + for _ in v { + count += 1; + } + assert_eq!(count, 3); + + // Same for consuming from some other Iterator. + let (tx, rx) = ::std::sync::mpsc::channel(); + tx.send(()).unwrap(); + drop(tx); + + count = 0; + for _ in rx.iter() { + count += 1; + } + assert_eq!(count, 1); +} + +fn multiline_sugg() { + let v: Vec = vec![2]; + + v + .into_iter() + .map(|i| i * 2) + .filter(|i| i % 2 == 0) + .map(|_| ()) + .next() + .unwrap(); +} + +#[derive(Copy, Clone)] +pub struct ContainsUnit(()); // should be fine diff --git a/src/tools/clippy/tests/ui/let_unit.rs b/src/tools/clippy/tests/ui/let_unit.rs new file mode 100644 index 0000000000000..af5b1fb2ac7e4 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_unit.rs @@ -0,0 +1,63 @@ +// run-rustfix + +#![warn(clippy::let_unit_value)] +#![allow(clippy::no_effect)] +#![allow(unused_variables)] + +macro_rules! let_and_return { + ($n:expr) => {{ + let ret = $n; + }}; +} + +fn main() { + let _x = println!("x"); + let _y = 1; // this is fine + let _z = ((), 1); // this as well + if true { + let _a = (); + } + + consume_units_with_for_loop(); // should be fine as well + + multiline_sugg(); + + let_and_return!(()) // should be fine +} + +// Related to issue #1964 +fn consume_units_with_for_loop() { + // `for_let_unit` lint should not be triggered by consuming them using for loop. + let v = vec![(), (), ()]; + let mut count = 0; + for _ in v { + count += 1; + } + assert_eq!(count, 3); + + // Same for consuming from some other Iterator. + let (tx, rx) = ::std::sync::mpsc::channel(); + tx.send(()).unwrap(); + drop(tx); + + count = 0; + for _ in rx.iter() { + count += 1; + } + assert_eq!(count, 1); +} + +fn multiline_sugg() { + let v: Vec = vec![2]; + + let _ = v + .into_iter() + .map(|i| i * 2) + .filter(|i| i % 2 == 0) + .map(|_| ()) + .next() + .unwrap(); +} + +#[derive(Copy, Clone)] +pub struct ContainsUnit(()); // should be fine diff --git a/src/tools/clippy/tests/ui/let_unit.stderr b/src/tools/clippy/tests/ui/let_unit.stderr new file mode 100644 index 0000000000000..eb8482087bcc8 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_unit.stderr @@ -0,0 +1,38 @@ +error: this let-binding has unit value + --> $DIR/let_unit.rs:14:5 + | +LL | let _x = println!("x"); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");` + | + = note: `-D clippy::let-unit-value` implied by `-D warnings` + +error: this let-binding has unit value + --> $DIR/let_unit.rs:18:9 + | +LL | let _a = (); + | ^^^^^^^^^^^^ help: omit the `let` binding: `();` + +error: this let-binding has unit value + --> $DIR/let_unit.rs:53:5 + | +LL | / let _ = v +LL | | .into_iter() +LL | | .map(|i| i * 2) +LL | | .filter(|i| i % 2 == 0) +LL | | .map(|_| ()) +LL | | .next() +LL | | .unwrap(); + | |__________________^ + | +help: omit the `let` binding + | +LL | v +LL | .into_iter() +LL | .map(|i| i * 2) +LL | .filter(|i| i % 2 == 0) +LL | .map(|_| ()) +LL | .next() + ... + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/lint_without_lint_pass.rs b/src/tools/clippy/tests/ui/lint_without_lint_pass.rs new file mode 100644 index 0000000000000..beaef79a340af --- /dev/null +++ b/src/tools/clippy/tests/ui/lint_without_lint_pass.rs @@ -0,0 +1,44 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +extern crate rustc_lint; +use rustc_lint::LintPass; + +declare_tool_lint! { + pub clippy::TEST_LINT, + Warn, + "", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::TEST_LINT_REGISTERED, + Warn, + "", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::TEST_LINT_REGISTERED_ONLY_IMPL, + Warn, + "", + report_in_external_macro: true +} + +pub struct Pass; +impl LintPass for Pass { + fn name(&self) -> &'static str { + "TEST_LINT" + } +} + +declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]); + +pub struct Pass3; +impl_lint_pass!(Pass3 => [TEST_LINT_REGISTERED_ONLY_IMPL]); + +fn main() {} diff --git a/src/tools/clippy/tests/ui/lint_without_lint_pass.stderr b/src/tools/clippy/tests/ui/lint_without_lint_pass.stderr new file mode 100644 index 0000000000000..1257dae96d71c --- /dev/null +++ b/src/tools/clippy/tests/ui/lint_without_lint_pass.stderr @@ -0,0 +1,21 @@ +error: the lint `TEST_LINT` is not added to any `LintPass` + --> $DIR/lint_without_lint_pass.rs:11:1 + | +LL | / declare_tool_lint! { +LL | | pub clippy::TEST_LINT, +LL | | Warn, +LL | | "", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/lint_without_lint_pass.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::lint_without_lint_pass)]` implied by `#[deny(clippy::internal)]` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/literals.rs b/src/tools/clippy/tests/ui/literals.rs new file mode 100644 index 0000000000000..c299b16c8ce85 --- /dev/null +++ b/src/tools/clippy/tests/ui/literals.rs @@ -0,0 +1,36 @@ +// does not test any rustfixable lints + +#![warn(clippy::mixed_case_hex_literals)] +#![warn(clippy::zero_prefixed_literal)] +#![allow(clippy::unseparated_literal_suffix)] +#![allow(dead_code)] + +fn main() { + let ok1 = 0xABCD; + let ok3 = 0xab_cd; + let ok4 = 0xab_cd_i32; + let ok5 = 0xAB_CD_u32; + let ok5 = 0xAB_CD_isize; + let fail1 = 0xabCD; + let fail2 = 0xabCD_u32; + let fail2 = 0xabCD_isize; + let fail_multi_zero = 000_123usize; + + let ok9 = 0; + let ok10 = 0_i64; + let fail8 = 0123; + + let ok11 = 0o123; + let ok12 = 0b10_1010; + + let ok13 = 0xab_abcd; + let ok14 = 0xBAFE_BAFE; + let ok15 = 0xab_cabc_abca_bcab_cabc; + let ok16 = 0xFE_BAFE_ABAB_ABCD; + let ok17 = 0x123_4567_8901_usize; + let ok18 = 0xF; + + let fail19 = 12_3456_21; + let fail22 = 3__4___23; + let fail23 = 3__16___23; +} diff --git a/src/tools/clippy/tests/ui/literals.stderr b/src/tools/clippy/tests/ui/literals.stderr new file mode 100644 index 0000000000000..0b3af2d8bc35f --- /dev/null +++ b/src/tools/clippy/tests/ui/literals.stderr @@ -0,0 +1,73 @@ +error: inconsistent casing in hexadecimal literal + --> $DIR/literals.rs:14:17 + | +LL | let fail1 = 0xabCD; + | ^^^^^^ + | + = note: `-D clippy::mixed-case-hex-literals` implied by `-D warnings` + +error: inconsistent casing in hexadecimal literal + --> $DIR/literals.rs:15:17 + | +LL | let fail2 = 0xabCD_u32; + | ^^^^^^^^^^ + +error: inconsistent casing in hexadecimal literal + --> $DIR/literals.rs:16:17 + | +LL | let fail2 = 0xabCD_isize; + | ^^^^^^^^^^^^ + +error: this is a decimal constant + --> $DIR/literals.rs:17:27 + | +LL | let fail_multi_zero = 000_123usize; + | ^^^^^^^^^^^^ + | + = note: `-D clippy::zero-prefixed-literal` implied by `-D warnings` +help: if you mean to use a decimal constant, remove the `0` to avoid confusion + | +LL | let fail_multi_zero = 123usize; + | ^^^^^^^^ +help: if you mean to use an octal constant, use `0o` + | +LL | let fail_multi_zero = 0o123usize; + | ^^^^^^^^^^ + +error: this is a decimal constant + --> $DIR/literals.rs:21:17 + | +LL | let fail8 = 0123; + | ^^^^ + | +help: if you mean to use a decimal constant, remove the `0` to avoid confusion + | +LL | let fail8 = 123; + | ^^^ +help: if you mean to use an octal constant, use `0o` + | +LL | let fail8 = 0o123; + | ^^^^^ + +error: digits grouped inconsistently by underscores + --> $DIR/literals.rs:33:18 + | +LL | let fail19 = 12_3456_21; + | ^^^^^^^^^^ help: consider: `12_345_621` + | + = note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings` + +error: digits grouped inconsistently by underscores + --> $DIR/literals.rs:34:18 + | +LL | let fail22 = 3__4___23; + | ^^^^^^^^^ help: consider: `3_423` + +error: digits grouped inconsistently by underscores + --> $DIR/literals.rs:35:18 + | +LL | let fail23 = 3__16___23; + | ^^^^^^^^^^ help: consider: `31_623` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/logic_bug.rs b/src/tools/clippy/tests/ui/logic_bug.rs new file mode 100644 index 0000000000000..b4163d776e73a --- /dev/null +++ b/src/tools/clippy/tests/ui/logic_bug.rs @@ -0,0 +1,26 @@ +#![allow(unused, clippy::many_single_char_names)] +#![warn(clippy::logic_bug)] + +fn main() { + let a: bool = unimplemented!(); + let b: bool = unimplemented!(); + let c: bool = unimplemented!(); + let d: bool = unimplemented!(); + let e: bool = unimplemented!(); + let _ = a && b || a; + let _ = !(a && b); + let _ = false && a; + // don't lint on cfgs + let _ = cfg!(you_shall_not_not_pass) && a; + let _ = a || !b || !c || !d || !e; + let _ = !(a && b || c); +} + +fn equality_stuff() { + let a: i32 = unimplemented!(); + let b: i32 = unimplemented!(); + let _ = a == b && a != b; + let _ = a < b && a >= b; + let _ = a > b && a <= b; + let _ = a > b && a == b; +} diff --git a/src/tools/clippy/tests/ui/logic_bug.stderr b/src/tools/clippy/tests/ui/logic_bug.stderr new file mode 100644 index 0000000000000..8f55e1c8ad859 --- /dev/null +++ b/src/tools/clippy/tests/ui/logic_bug.stderr @@ -0,0 +1,63 @@ +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:10:13 + | +LL | let _ = a && b || a; + | ^^^^^^^^^^^ help: it would look like the following: `a` + | + = note: `-D clippy::logic-bug` implied by `-D warnings` +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:10:18 + | +LL | let _ = a && b || a; + | ^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:12:13 + | +LL | let _ = false && a; + | ^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:12:22 + | +LL | let _ = false && a; + | ^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:22:13 + | +LL | let _ = a == b && a != b; + | ^^^^^^^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:22:13 + | +LL | let _ = a == b && a != b; + | ^^^^^^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:23:13 + | +LL | let _ = a < b && a >= b; + | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:23:13 + | +LL | let _ = a < b && a >= b; + | ^^^^^ + +error: this boolean expression contains a logic bug + --> $DIR/logic_bug.rs:24:13 + | +LL | let _ = a > b && a <= b; + | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` + | +help: this expression can be optimized out by applying boolean operations to the outer expression + --> $DIR/logic_bug.rs:24:13 + | +LL | let _ = a > b && a <= b; + | ^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/lossy_float_literal.fixed b/src/tools/clippy/tests/ui/lossy_float_literal.fixed new file mode 100644 index 0000000000000..24e372354fc05 --- /dev/null +++ b/src/tools/clippy/tests/ui/lossy_float_literal.fixed @@ -0,0 +1,35 @@ +// run-rustfix +#![warn(clippy::lossy_float_literal)] + +fn main() { + // Lossy whole-number float literals + let _: f32 = 16_777_216.0; + let _: f32 = 16_777_220.0; + let _: f32 = 16_777_220.0; + let _: f32 = 16_777_220.0; + let _ = 16_777_220_f32; + let _: f32 = -16_777_220.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = 9_007_199_254_740_992.0; + let _ = 9_007_199_254_740_992_f64; + let _: f64 = -9_007_199_254_740_992.0; + + // Lossless whole number float literals + let _: f32 = 16_777_216.0; + let _: f32 = 16_777_218.0; + let _: f32 = 16_777_220.0; + let _: f32 = -16_777_216.0; + let _: f32 = -16_777_220.0; + let _: f64 = 16_777_217.0; + let _: f64 = -16_777_217.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = -9_007_199_254_740_992.0; + + // Ignored whole number float literals + let _: f32 = 1e25; + let _: f32 = 1E25; + let _: f64 = 1e99; + let _: f64 = 1E99; + let _: f32 = 0.1; +} diff --git a/src/tools/clippy/tests/ui/lossy_float_literal.rs b/src/tools/clippy/tests/ui/lossy_float_literal.rs new file mode 100644 index 0000000000000..3dcf98fa0bdda --- /dev/null +++ b/src/tools/clippy/tests/ui/lossy_float_literal.rs @@ -0,0 +1,35 @@ +// run-rustfix +#![warn(clippy::lossy_float_literal)] + +fn main() { + // Lossy whole-number float literals + let _: f32 = 16_777_217.0; + let _: f32 = 16_777_219.0; + let _: f32 = 16_777_219.; + let _: f32 = 16_777_219.000; + let _ = 16_777_219f32; + let _: f32 = -16_777_219.0; + let _: f64 = 9_007_199_254_740_993.0; + let _: f64 = 9_007_199_254_740_993.; + let _: f64 = 9_007_199_254_740_993.00; + let _ = 9_007_199_254_740_993f64; + let _: f64 = -9_007_199_254_740_993.0; + + // Lossless whole number float literals + let _: f32 = 16_777_216.0; + let _: f32 = 16_777_218.0; + let _: f32 = 16_777_220.0; + let _: f32 = -16_777_216.0; + let _: f32 = -16_777_220.0; + let _: f64 = 16_777_217.0; + let _: f64 = -16_777_217.0; + let _: f64 = 9_007_199_254_740_992.0; + let _: f64 = -9_007_199_254_740_992.0; + + // Ignored whole number float literals + let _: f32 = 1e25; + let _: f32 = 1E25; + let _: f64 = 1e99; + let _: f64 = 1E99; + let _: f32 = 0.1; +} diff --git a/src/tools/clippy/tests/ui/lossy_float_literal.stderr b/src/tools/clippy/tests/ui/lossy_float_literal.stderr new file mode 100644 index 0000000000000..d2193c0c81955 --- /dev/null +++ b/src/tools/clippy/tests/ui/lossy_float_literal.stderr @@ -0,0 +1,70 @@ +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:6:18 + | +LL | let _: f32 = 16_777_217.0; + | ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_216.0` + | + = note: `-D clippy::lossy-float-literal` implied by `-D warnings` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:7:18 + | +LL | let _: f32 = 16_777_219.0; + | ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:8:18 + | +LL | let _: f32 = 16_777_219.; + | ^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:9:18 + | +LL | let _: f32 = 16_777_219.000; + | ^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:10:13 + | +LL | let _ = 16_777_219f32; + | ^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220_f32` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:11:19 + | +LL | let _: f32 = -16_777_219.0; + | ^^^^^^^^^^^^ help: consider changing the type or replacing it with: `16_777_220.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:12:18 + | +LL | let _: f64 = 9_007_199_254_740_993.0; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:13:18 + | +LL | let _: f64 = 9_007_199_254_740_993.; + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:14:18 + | +LL | let _: f64 = 9_007_199_254_740_993.00; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:15:13 + | +LL | let _ = 9_007_199_254_740_993f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992_f64` + +error: literal cannot be represented as the underlying type without loss of precision + --> $DIR/lossy_float_literal.rs:16:19 + | +LL | let _: f64 = -9_007_199_254_740_993.0; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or replacing it with: `9_007_199_254_740_992.0` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/macro_use_imports.rs b/src/tools/clippy/tests/ui/macro_use_imports.rs new file mode 100644 index 0000000000000..60c64ee8146e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/macro_use_imports.rs @@ -0,0 +1,11 @@ +// edition:2018 +#![warn(clippy::macro_use_imports)] + +use std::collections::HashMap; +#[macro_use] +use std::prelude; + +fn main() { + let _ = HashMap::::new(); + println!(); +} diff --git a/src/tools/clippy/tests/ui/macro_use_imports.stderr b/src/tools/clippy/tests/ui/macro_use_imports.stderr new file mode 100644 index 0000000000000..b5e3dbec57277 --- /dev/null +++ b/src/tools/clippy/tests/ui/macro_use_imports.stderr @@ -0,0 +1,10 @@ +error: `macro_use` attributes are no longer needed in the Rust 2018 edition + --> $DIR/macro_use_imports.rs:5:1 + | +LL | #[macro_use] + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use std::prelude::` + | + = note: `-D clippy::macro-use-imports` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/manual_memcpy.rs b/src/tools/clippy/tests/ui/manual_memcpy.rs new file mode 100644 index 0000000000000..aa347288875d5 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_memcpy.rs @@ -0,0 +1,110 @@ +#![warn(clippy::needless_range_loop, clippy::manual_memcpy)] + +const LOOP_OFFSET: usize = 5000; + +pub fn manual_copy(src: &[i32], dst: &mut [i32], dst2: &mut [i32]) { + // plain manual memcpy + for i in 0..src.len() { + dst[i] = src[i]; + } + + // dst offset memcpy + for i in 0..src.len() { + dst[i + 10] = src[i]; + } + + // src offset memcpy + for i in 0..src.len() { + dst[i] = src[i + 10]; + } + + // src offset memcpy + for i in 11..src.len() { + dst[i] = src[i - 10]; + } + + // overwrite entire dst + for i in 0..dst.len() { + dst[i] = src[i]; + } + + // manual copy with branch - can't easily convert to memcpy! + for i in 0..src.len() { + dst[i] = src[i]; + if dst[i] > 5 { + break; + } + } + + // multiple copies - suggest two memcpy statements + for i in 10..256 { + dst[i] = src[i - 5]; + dst2[i + 500] = src[i] + } + + // this is a reversal - the copy lint shouldn't be triggered + for i in 10..LOOP_OFFSET { + dst[i + LOOP_OFFSET] = src[LOOP_OFFSET - i]; + } + + let some_var = 5; + // Offset in variable + for i in 10..LOOP_OFFSET { + dst[i + LOOP_OFFSET] = src[i - some_var]; + } + + // Non continuous copy - don't trigger lint + for i in 0..10 { + dst[i + i] = src[i]; + } + + let src_vec = vec![1, 2, 3, 4, 5]; + let mut dst_vec = vec![0, 0, 0, 0, 0]; + + // make sure vectors are supported + for i in 0..src_vec.len() { + dst_vec[i] = src_vec[i]; + } + + // lint should not trigger when either + // source or destination type is not + // slice-like, like DummyStruct + struct DummyStruct(i32); + + impl ::std::ops::Index for DummyStruct { + type Output = i32; + + fn index(&self, _: usize) -> &i32 { + &self.0 + } + } + + let src = DummyStruct(5); + let mut dst_vec = vec![0; 10]; + + for i in 0..10 { + dst_vec[i] = src[i]; + } + + // Simplify suggestion (issue #3004) + let src = [0, 1, 2, 3, 4]; + let mut dst = [0, 0, 0, 0, 0, 0]; + let from = 1; + + for i in from..from + src.len() { + dst[i] = src[i - from]; + } + + for i in from..from + 3 { + dst[i] = src[i - from]; + } +} + +#[warn(clippy::needless_range_loop, clippy::manual_memcpy)] +pub fn manual_clone(src: &[String], dst: &mut [String]) { + for i in 0..src.len() { + dst[i] = src[i].clone(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_memcpy.stderr b/src/tools/clippy/tests/ui/manual_memcpy.stderr new file mode 100644 index 0000000000000..3dbb2155d4de7 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_memcpy.stderr @@ -0,0 +1,76 @@ +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:7:14 + | +LL | for i in 0..src.len() { + | ^^^^^^^^^^^^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[..])` + | + = note: `-D clippy::manual-memcpy` implied by `-D warnings` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:12:14 + | +LL | for i in 0..src.len() { + | ^^^^^^^^^^^^ help: try replacing the loop by: `dst[10..(src.len() + 10)].clone_from_slice(&src[..])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:17:14 + | +LL | for i in 0..src.len() { + | ^^^^^^^^^^^^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[10..])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:22:14 + | +LL | for i in 11..src.len() { + | ^^^^^^^^^^^^^ help: try replacing the loop by: `dst[11..src.len()].clone_from_slice(&src[(11 - 10)..(src.len() - 10)])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:27:14 + | +LL | for i in 0..dst.len() { + | ^^^^^^^^^^^^ help: try replacing the loop by: `dst.clone_from_slice(&src[..dst.len()])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:40:14 + | +LL | for i in 10..256 { + | ^^^^^^^ + | +help: try replacing the loop by + | +LL | for i in dst[10..256].clone_from_slice(&src[(10 - 5)..(256 - 5)]) +LL | dst2[(10 + 500)..(256 + 500)].clone_from_slice(&src[10..256]) { + | + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:52:14 + | +LL | for i in 10..LOOP_OFFSET { + | ^^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[(10 + LOOP_OFFSET)..(LOOP_OFFSET + LOOP_OFFSET)].clone_from_slice(&src[(10 - some_var)..(LOOP_OFFSET - some_var)])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:65:14 + | +LL | for i in 0..src_vec.len() { + | ^^^^^^^^^^^^^^^^ help: try replacing the loop by: `dst_vec[..src_vec.len()].clone_from_slice(&src_vec[..])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:94:14 + | +LL | for i in from..from + src.len() { + | ^^^^^^^^^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[from..from + src.len()].clone_from_slice(&src[0..(from + src.len() - from)])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:98:14 + | +LL | for i in from..from + 3 { + | ^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[from..from + 3].clone_from_slice(&src[0..(from + 3 - from)])` + +error: it looks like you're manually copying between slices + --> $DIR/manual_memcpy.rs:105:14 + | +LL | for i in 0..src.len() { + | ^^^^^^^^^^^^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[..])` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_saturating_arithmetic.fixed b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.fixed new file mode 100644 index 0000000000000..c4f53c446c9f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![allow(unused_imports)] + +use std::{i128, i32, u128, u32}; + +fn main() { + let _ = 1u32.saturating_add(1); + let _ = 1u32.saturating_add(1); + let _ = 1u8.saturating_add(1); + let _ = 1u128.saturating_add(1); + let _ = 1u32.checked_add(1).unwrap_or(1234); // ok + let _ = 1u8.checked_add(1).unwrap_or(0); // ok + let _ = 1u32.saturating_mul(1); + + let _ = 1u32.saturating_sub(1); + let _ = 1u32.saturating_sub(1); + let _ = 1u8.saturating_sub(1); + let _ = 1u32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1u8.checked_sub(1).unwrap_or(255); // ok + + let _ = 1i32.saturating_add(1); + let _ = 1i32.saturating_add(1); + let _ = 1i8.saturating_add(1); + let _ = 1i128.saturating_add(1); + let _ = 1i32.saturating_add(-1); + let _ = 1i32.saturating_add(-1); + let _ = 1i8.saturating_add(-1); + let _ = 1i128.saturating_add(-1); + let _ = 1i32.checked_add(1).unwrap_or(1234); // ok + let _ = 1i8.checked_add(1).unwrap_or(-128); // ok + let _ = 1i8.checked_add(-1).unwrap_or(127); // ok + + let _ = 1i32.saturating_sub(1); + let _ = 1i32.saturating_sub(1); + let _ = 1i8.saturating_sub(1); + let _ = 1i128.saturating_sub(1); + let _ = 1i32.saturating_sub(-1); + let _ = 1i32.saturating_sub(-1); + let _ = 1i8.saturating_sub(-1); + let _ = 1i128.saturating_sub(-1); + let _ = 1i32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1i8.checked_sub(1).unwrap_or(127); // ok + let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok +} diff --git a/src/tools/clippy/tests/ui/manual_saturating_arithmetic.rs b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.rs new file mode 100644 index 0000000000000..cd83cf6e65e94 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.rs @@ -0,0 +1,55 @@ +// run-rustfix + +#![allow(unused_imports)] + +use std::{i128, i32, u128, u32}; + +fn main() { + let _ = 1u32.checked_add(1).unwrap_or(u32::max_value()); + let _ = 1u32.checked_add(1).unwrap_or(u32::MAX); + let _ = 1u8.checked_add(1).unwrap_or(255); + let _ = 1u128 + .checked_add(1) + .unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455); + let _ = 1u32.checked_add(1).unwrap_or(1234); // ok + let _ = 1u8.checked_add(1).unwrap_or(0); // ok + let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX); + + let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value()); + let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN); + let _ = 1u8.checked_sub(1).unwrap_or(0); + let _ = 1u32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1u8.checked_sub(1).unwrap_or(255); // ok + + let _ = 1i32.checked_add(1).unwrap_or(i32::max_value()); + let _ = 1i32.checked_add(1).unwrap_or(i32::MAX); + let _ = 1i8.checked_add(1).unwrap_or(127); + let _ = 1i128 + .checked_add(1) + .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value()); + let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN); + let _ = 1i8.checked_add(-1).unwrap_or(-128); + let _ = 1i128 + .checked_add(-1) + .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + let _ = 1i32.checked_add(1).unwrap_or(1234); // ok + let _ = 1i8.checked_add(1).unwrap_or(-128); // ok + let _ = 1i8.checked_add(-1).unwrap_or(127); // ok + + let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value()); + let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN); + let _ = 1i8.checked_sub(1).unwrap_or(-128); + let _ = 1i128 + .checked_sub(1) + .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value()); + let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX); + let _ = 1i8.checked_sub(-1).unwrap_or(127); + let _ = 1i128 + .checked_sub(-1) + .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + let _ = 1i32.checked_sub(1).unwrap_or(1234); // ok + let _ = 1i8.checked_sub(1).unwrap_or(127); // ok + let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok +} diff --git a/src/tools/clippy/tests/ui/manual_saturating_arithmetic.stderr b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.stderr new file mode 100644 index 0000000000000..d985f2e754bc3 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_saturating_arithmetic.stderr @@ -0,0 +1,163 @@ +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:8:13 + | +LL | let _ = 1u32.checked_add(1).unwrap_or(u32::max_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u32.saturating_add(1)` + | + = note: `-D clippy::manual-saturating-arithmetic` implied by `-D warnings` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:9:13 + | +LL | let _ = 1u32.checked_add(1).unwrap_or(u32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u32.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:10:13 + | +LL | let _ = 1u8.checked_add(1).unwrap_or(255); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1u8.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:11:13 + | +LL | let _ = 1u128 + | _____________^ +LL | | .checked_add(1) +LL | | .unwrap_or(340_282_366_920_938_463_463_374_607_431_768_211_455); + | |_______________________________________________________________________^ help: try using `saturating_add`: `1u128.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:16:13 + | +LL | let _ = 1u32.checked_mul(1).unwrap_or(u32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_mul`: `1u32.saturating_mul(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:18:13 + | +LL | let _ = 1u32.checked_sub(1).unwrap_or(u32::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:19:13 + | +LL | let _ = 1u32.checked_sub(1).unwrap_or(u32::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:20:13 + | +LL | let _ = 1u8.checked_sub(1).unwrap_or(0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1u8.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:24:13 + | +LL | let _ = 1i32.checked_add(1).unwrap_or(i32::max_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:25:13 + | +LL | let _ = 1i32.checked_add(1).unwrap_or(i32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:26:13 + | +LL | let _ = 1i8.checked_add(1).unwrap_or(127); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i8.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:27:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_add(1) +LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + | |_______________________________________________________________________^ help: try using `saturating_add`: `1i128.saturating_add(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:30:13 + | +LL | let _ = 1i32.checked_add(-1).unwrap_or(i32::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:31:13 + | +LL | let _ = 1i32.checked_add(-1).unwrap_or(i32::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i32.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:32:13 + | +LL | let _ = 1i8.checked_add(-1).unwrap_or(-128); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_add`: `1i8.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:33:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_add(-1) +LL | | .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + | |________________________________________________________________________^ help: try using `saturating_add`: `1i128.saturating_add(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:40:13 + | +LL | let _ = 1i32.checked_sub(1).unwrap_or(i32::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:41:13 + | +LL | let _ = 1i32.checked_sub(1).unwrap_or(i32::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:42:13 + | +LL | let _ = 1i8.checked_sub(1).unwrap_or(-128); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i8.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:43:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_sub(1) +LL | | .unwrap_or(-170_141_183_460_469_231_731_687_303_715_884_105_728); + | |________________________________________________________________________^ help: try using `saturating_sub`: `1i128.saturating_sub(1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:46:13 + | +LL | let _ = 1i32.checked_sub(-1).unwrap_or(i32::max_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:47:13 + | +LL | let _ = 1i32.checked_sub(-1).unwrap_or(i32::MAX); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i32.saturating_sub(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:48:13 + | +LL | let _ = 1i8.checked_sub(-1).unwrap_or(127); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `saturating_sub`: `1i8.saturating_sub(-1)` + +error: manual saturating arithmetic + --> $DIR/manual_saturating_arithmetic.rs:49:13 + | +LL | let _ = 1i128 + | _____________^ +LL | | .checked_sub(-1) +LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727); + | |_______________________________________________________________________^ help: try using `saturating_sub`: `1i128.saturating_sub(-1)` + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/many_single_char_names.rs b/src/tools/clippy/tests/ui/many_single_char_names.rs new file mode 100644 index 0000000000000..80800e487248f --- /dev/null +++ b/src/tools/clippy/tests/ui/many_single_char_names.rs @@ -0,0 +1,73 @@ +#[warn(clippy::many_single_char_names)] + +fn bla() { + let a: i32; + let (b, c, d): (i32, i64, i16); + { + { + let cdefg: i32; + let blar: i32; + } + { + let e: i32; + } + { + let e: i32; + let f: i32; + } + match 5 { + 1 => println!(), + e => panic!(), + } + match 5 { + 1 => println!(), + _ => panic!(), + } + } +} + +fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} + +fn bindings2() { + let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); +} + +fn shadowing() { + let a = 0i32; + let a = 0i32; + let a = 0i32; + let a = 0i32; + let a = 0i32; + let a = 0i32; + { + let a = 0i32; + } +} + +fn patterns() { + enum Z { + A(i32), + B(i32), + C(i32), + D(i32), + E(i32), + F(i32), + } + + // These should not trigger a warning, since the pattern bindings are a new scope. + match Z::A(0) { + Z::A(a) => {}, + Z::B(b) => {}, + Z::C(c) => {}, + Z::D(d) => {}, + Z::E(e) => {}, + Z::F(f) => {}, + } +} + +#[allow(clippy::many_single_char_names)] +fn issue_3198_allow_works() { + let (a, b, c, d, e) = (0, 0, 0, 0, 0); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/many_single_char_names.stderr b/src/tools/clippy/tests/ui/many_single_char_names.stderr new file mode 100644 index 0000000000000..27e62e641ade9 --- /dev/null +++ b/src/tools/clippy/tests/ui/many_single_char_names.stderr @@ -0,0 +1,51 @@ +error: 5 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:4:9 + | +LL | let a: i32; + | ^ +LL | let (b, c, d): (i32, i64, i16); + | ^ ^ ^ +... +LL | let e: i32; + | ^ + | + = note: `-D clippy::many-single-char-names` implied by `-D warnings` + +error: 6 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:4:9 + | +LL | let a: i32; + | ^ +LL | let (b, c, d): (i32, i64, i16); + | ^ ^ ^ +... +LL | let e: i32; + | ^ +LL | let f: i32; + | ^ + +error: 5 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:4:9 + | +LL | let a: i32; + | ^ +LL | let (b, c, d): (i32, i64, i16); + | ^ ^ ^ +... +LL | e => panic!(), + | ^ + +error: 8 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:29:13 + | +LL | fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} + | ^ ^ ^ ^ ^ ^ ^ ^ + +error: 8 bindings with single-character names in scope + --> $DIR/many_single_char_names.rs:32:10 + | +LL | let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); + | ^ ^ ^ ^ ^ ^ ^ ^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/map_clone.fixed b/src/tools/clippy/tests/ui/map_clone.fixed new file mode 100644 index 0000000000000..81c7f659efb1f --- /dev/null +++ b/src/tools/clippy/tests/ui/map_clone.fixed @@ -0,0 +1,47 @@ +// run-rustfix +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::iter_cloned_collect)] +#![allow(clippy::clone_on_copy, clippy::redundant_clone)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::redundant_closure_for_method_calls)] +#![allow(clippy::many_single_char_names)] + +fn main() { + let _: Vec = vec![5_i8; 6].iter().copied().collect(); + let _: Vec = vec![String::new()].iter().cloned().collect(); + let _: Vec = vec![42, 43].iter().copied().collect(); + let _: Option = Some(Box::new(16)).map(|b| *b); + let _: Option = Some(&16).copied(); + let _: Option = Some(&1).copied(); + + // Don't lint these + let v = vec![5_i8; 6]; + let a = 0; + let b = &a; + let _ = v.iter().map(|_x| *b); + let _ = v.iter().map(|_x| a.clone()); + let _ = v.iter().map(|&_x| a); + + // Issue #498 + let _ = std::env::args(); + + // Issue #4824 item types that aren't references + { + use std::rc::Rc; + + let o: Option> = Some(Rc::new(0_u32)); + let _: Option = o.map(|x| *x); + let v: Vec> = vec![Rc::new(0_u32)]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + } + + // Issue #5524 mutable references + { + let mut c = 42; + let v = vec![&mut c]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + let mut d = 21; + let v = vec![&mut d]; + let _: Vec = v.into_iter().map(|&mut x| x).collect(); + } +} diff --git a/src/tools/clippy/tests/ui/map_clone.rs b/src/tools/clippy/tests/ui/map_clone.rs new file mode 100644 index 0000000000000..8ed164f0ed51d --- /dev/null +++ b/src/tools/clippy/tests/ui/map_clone.rs @@ -0,0 +1,47 @@ +// run-rustfix +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::iter_cloned_collect)] +#![allow(clippy::clone_on_copy, clippy::redundant_clone)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::redundant_closure_for_method_calls)] +#![allow(clippy::many_single_char_names)] + +fn main() { + let _: Vec = vec![5_i8; 6].iter().map(|x| *x).collect(); + let _: Vec = vec![String::new()].iter().map(|x| x.clone()).collect(); + let _: Vec = vec![42, 43].iter().map(|&x| x).collect(); + let _: Option = Some(Box::new(16)).map(|b| *b); + let _: Option = Some(&16).map(|b| *b); + let _: Option = Some(&1).map(|x| x.clone()); + + // Don't lint these + let v = vec![5_i8; 6]; + let a = 0; + let b = &a; + let _ = v.iter().map(|_x| *b); + let _ = v.iter().map(|_x| a.clone()); + let _ = v.iter().map(|&_x| a); + + // Issue #498 + let _ = std::env::args().map(|v| v.clone()); + + // Issue #4824 item types that aren't references + { + use std::rc::Rc; + + let o: Option> = Some(Rc::new(0_u32)); + let _: Option = o.map(|x| *x); + let v: Vec> = vec![Rc::new(0_u32)]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + } + + // Issue #5524 mutable references + { + let mut c = 42; + let v = vec![&mut c]; + let _: Vec = v.into_iter().map(|x| *x).collect(); + let mut d = 21; + let v = vec![&mut d]; + let _: Vec = v.into_iter().map(|&mut x| x).collect(); + } +} diff --git a/src/tools/clippy/tests/ui/map_clone.stderr b/src/tools/clippy/tests/ui/map_clone.stderr new file mode 100644 index 0000000000000..9eec6928e8cee --- /dev/null +++ b/src/tools/clippy/tests/ui/map_clone.stderr @@ -0,0 +1,40 @@ +error: You are using an explicit closure for copying elements + --> $DIR/map_clone.rs:10:22 + | +LL | let _: Vec = vec![5_i8; 6].iter().map(|x| *x).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `vec![5_i8; 6].iter().copied()` + | + = note: `-D clippy::map-clone` implied by `-D warnings` + +error: You are using an explicit closure for cloning elements + --> $DIR/map_clone.rs:11:26 + | +LL | let _: Vec = vec![String::new()].iter().map(|x| x.clone()).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `cloned` method: `vec![String::new()].iter().cloned()` + +error: You are using an explicit closure for copying elements + --> $DIR/map_clone.rs:12:23 + | +LL | let _: Vec = vec![42, 43].iter().map(|&x| x).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `vec![42, 43].iter().copied()` + +error: You are using an explicit closure for copying elements + --> $DIR/map_clone.rs:14:26 + | +LL | let _: Option = Some(&16).map(|b| *b); + | ^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `Some(&16).copied()` + +error: You are using an explicit closure for copying elements + --> $DIR/map_clone.rs:15:25 + | +LL | let _: Option = Some(&1).map(|x| x.clone()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: Consider calling the dedicated `copied` method: `Some(&1).copied()` + +error: You are needlessly cloning iterator elements + --> $DIR/map_clone.rs:26:29 + | +LL | let _ = std::env::args().map(|v| v.clone()); + | ^^^^^^^^^^^^^^^^^^^ help: Remove the `map` call + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/map_flatten.fixed b/src/tools/clippy/tests/ui/map_flatten.fixed new file mode 100644 index 0000000000000..7ac368878ab7e --- /dev/null +++ b/src/tools/clippy/tests/ui/map_flatten.fixed @@ -0,0 +1,9 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::missing_docs_in_private_items)] + +fn main() { + let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect(); + let _: Option<_> = (Some(Some(1))).and_then(|x| x); +} diff --git a/src/tools/clippy/tests/ui/map_flatten.rs b/src/tools/clippy/tests/ui/map_flatten.rs new file mode 100644 index 0000000000000..a608601039ce7 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_flatten.rs @@ -0,0 +1,9 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::missing_docs_in_private_items)] + +fn main() { + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); +} diff --git a/src/tools/clippy/tests/ui/map_flatten.stderr b/src/tools/clippy/tests/ui/map_flatten.stderr new file mode 100644 index 0000000000000..3cf2abd5b6d85 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_flatten.stderr @@ -0,0 +1,16 @@ +error: called `map(..).flatten()` on an `Iterator`. This is more succinctly expressed by calling `.flat_map(..)` + --> $DIR/map_flatten.rs:7:21 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `vec![5_i8; 6].into_iter().flat_map(|x| 0..x)` + | + = note: `-D clippy::map-flatten` implied by `-D warnings` + +error: called `map(..).flatten()` on an `Option`. This is more succinctly expressed by calling `.and_then(..)` + --> $DIR/map_flatten.rs:8:24 + | +LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `(Some(Some(1))).and_then(|x| x)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/map_unit_fn.rs b/src/tools/clippy/tests/ui/map_unit_fn.rs new file mode 100644 index 0000000000000..9a74da4e3b8b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/map_unit_fn.rs @@ -0,0 +1,11 @@ +#![allow(unused)] +struct Mappable {} + +impl Mappable { + pub fn map(&self) {} +} + +fn main() { + let m = Mappable {}; + m.map(); +} diff --git a/src/tools/clippy/tests/ui/match_as_ref.fixed b/src/tools/clippy/tests/ui/match_as_ref.fixed new file mode 100644 index 0000000000000..c61eb9216643e --- /dev/null +++ b/src/tools/clippy/tests/ui/match_as_ref.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::match_as_ref)] + +fn match_as_ref() { + let owned: Option<()> = None; + let borrowed: Option<&()> = owned.as_ref(); + + let mut mut_owned: Option<()> = None; + let borrow_mut: Option<&mut ()> = mut_owned.as_mut(); +} + +mod issue4437 { + use std::{error::Error, fmt, num::ParseIntError}; + + #[derive(Debug)] + struct E { + source: Option, + } + + impl Error for E { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source.as_ref().map(|x| x as _) + } + } + + impl fmt::Display for E { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_as_ref.rs b/src/tools/clippy/tests/ui/match_as_ref.rs new file mode 100644 index 0000000000000..2fbd0b255faae --- /dev/null +++ b/src/tools/clippy/tests/ui/match_as_ref.rs @@ -0,0 +1,44 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::match_as_ref)] + +fn match_as_ref() { + let owned: Option<()> = None; + let borrowed: Option<&()> = match owned { + None => None, + Some(ref v) => Some(v), + }; + + let mut mut_owned: Option<()> = None; + let borrow_mut: Option<&mut ()> = match mut_owned { + None => None, + Some(ref mut v) => Some(v), + }; +} + +mod issue4437 { + use std::{error::Error, fmt, num::ParseIntError}; + + #[derive(Debug)] + struct E { + source: Option, + } + + impl Error for E { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self.source { + Some(ref s) => Some(s), + None => None, + } + } + } + + impl fmt::Display for E { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_as_ref.stderr b/src/tools/clippy/tests/ui/match_as_ref.stderr new file mode 100644 index 0000000000000..c3b62849cb33f --- /dev/null +++ b/src/tools/clippy/tests/ui/match_as_ref.stderr @@ -0,0 +1,33 @@ +error: use `as_ref()` instead + --> $DIR/match_as_ref.rs:8:33 + | +LL | let borrowed: Option<&()> = match owned { + | _________________________________^ +LL | | None => None, +LL | | Some(ref v) => Some(v), +LL | | }; + | |_____^ help: try this: `owned.as_ref()` + | + = note: `-D clippy::match-as-ref` implied by `-D warnings` + +error: use `as_mut()` instead + --> $DIR/match_as_ref.rs:14:39 + | +LL | let borrow_mut: Option<&mut ()> = match mut_owned { + | _______________________________________^ +LL | | None => None, +LL | | Some(ref mut v) => Some(v), +LL | | }; + | |_____^ help: try this: `mut_owned.as_mut()` + +error: use `as_ref()` instead + --> $DIR/match_as_ref.rs:30:13 + | +LL | / match self.source { +LL | | Some(ref s) => Some(s), +LL | | None => None, +LL | | } + | |_____________^ help: try this: `self.source.as_ref().map(|x| x as _)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/match_bool.rs b/src/tools/clippy/tests/ui/match_bool.rs new file mode 100644 index 0000000000000..9ed55ca7ae7f9 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_bool.rs @@ -0,0 +1,55 @@ +#![deny(clippy::match_bool)] + +fn match_bool() { + let test: bool = true; + + match test { + true => 0, + false => 42, + }; + + let option = 1; + match option == 1 { + true => 1, + false => 0, + }; + + match test { + true => (), + false => { + println!("Noooo!"); + }, + }; + + match test { + false => { + println!("Noooo!"); + }, + _ => (), + }; + + match test && test { + false => { + println!("Noooo!"); + }, + _ => (), + }; + + match test { + false => { + println!("Noooo!"); + }, + true => { + println!("Yes!"); + }, + }; + + // Not linted + match option { + 1..=10 => 1, + 11..=20 => 2, + _ => 3, + }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_bool.stderr b/src/tools/clippy/tests/ui/match_bool.stderr new file mode 100644 index 0000000000000..1ad78c740c68b --- /dev/null +++ b/src/tools/clippy/tests/ui/match_bool.stderr @@ -0,0 +1,117 @@ +error: this boolean expression can be simplified + --> $DIR/match_bool.rs:31:11 + | +LL | match test && test { + | ^^^^^^^^^^^^ help: try: `test` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:6:5 + | +LL | / match test { +LL | | true => 0, +LL | | false => 42, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if test { 0 } else { 42 }` + | +note: the lint level is defined here + --> $DIR/match_bool.rs:1:9 + | +LL | #![deny(clippy::match_bool)] + | ^^^^^^^^^^^^^^^^^^ + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:12:5 + | +LL | / match option == 1 { +LL | | true => 1, +LL | | false => 0, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if option == 1 { 1 } else { 0 }` + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:17:5 + | +LL | / match test { +LL | | true => (), +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if !test { +LL | println!("Noooo!"); +LL | }; + | + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:24:5 + | +LL | / match test { +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +LL | | _ => (), +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if !test { +LL | println!("Noooo!"); +LL | }; + | + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:31:5 + | +LL | / match test && test { +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +LL | | _ => (), +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if !(test && test) { +LL | println!("Noooo!"); +LL | }; + | + +error: equal expressions as operands to `&&` + --> $DIR/match_bool.rs:31:11 + | +LL | match test && test { + | ^^^^^^^^^^^^ + | + = note: `#[deny(clippy::eq_op)]` on by default + +error: you seem to be trying to match on a boolean expression + --> $DIR/match_bool.rs:38:5 + | +LL | / match test { +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, +... | +LL | | }, +LL | | }; + | |_____^ + | +help: consider using an `if`/`else` expression + | +LL | if test { +LL | println!("Yes!"); +LL | } else { +LL | println!("Noooo!"); +LL | }; + | + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/match_on_vec_items.rs b/src/tools/clippy/tests/ui/match_on_vec_items.rs new file mode 100644 index 0000000000000..0bb39d77e461e --- /dev/null +++ b/src/tools/clippy/tests/ui/match_on_vec_items.rs @@ -0,0 +1,130 @@ +#![warn(clippy::match_on_vec_items)] + +fn match_with_wildcard() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 1; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn match_without_wildcard() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 2; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + num => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + [ref sub @ ..] => {}, + } +} + +fn match_wildcard_and_action() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => println!("Hello, World!"), + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => println!("Hello, World!"), + } +} + +fn match_vec_ref() { + let arr = &vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn match_with_get() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr.get(idx) { + Some(0) => println!("0"), + Some(1) => println!("1"), + _ => {}, + } + + // Ok + match arr.get(range) { + Some(&[0, 1]) => println!("0 1"), + Some(&[1, 2]) => println!("1 2"), + _ => {}, + } +} + +fn match_with_array() { + let arr = [0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Ok + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn main() { + match_with_wildcard(); + match_without_wildcard(); + match_wildcard_and_action(); + match_vec_ref(); + match_with_get(); + match_with_array(); +} diff --git a/src/tools/clippy/tests/ui/match_on_vec_items.stderr b/src/tools/clippy/tests/ui/match_on_vec_items.stderr new file mode 100644 index 0000000000000..49446d715abe2 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_on_vec_items.stderr @@ -0,0 +1,52 @@ +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:9:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + | + = note: `-D clippy::match-on-vec-items` implied by `-D warnings` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:16:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:29:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:36:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:49:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:56:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:69:11 + | +LL | match arr[idx] { + | ^^^^^^^^ help: try this: `arr.get(idx)` + +error: indexing into a vector may panic + --> $DIR/match_on_vec_items.rs:76:11 + | +LL | match arr[range] { + | ^^^^^^^^^^ help: try this: `arr.get(range)` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/match_overlapping_arm.rs b/src/tools/clippy/tests/ui/match_overlapping_arm.rs new file mode 100644 index 0000000000000..97789bb766f89 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_overlapping_arm.rs @@ -0,0 +1,82 @@ +#![feature(exclusive_range_pattern)] +#![feature(half_open_range_patterns)] +#![warn(clippy::match_overlapping_arm)] +#![allow(clippy::redundant_pattern_matching)] + +/// Tests for match_overlapping_arm + +fn overlapping() { + const FOO: u64 = 2; + + match 42 { + 0..=10 => println!("0 ... 10"), + 0..=11 => println!("0 ... 11"), + _ => (), + } + + match 42 { + 0..=5 => println!("0 ... 5"), + 6..=7 => println!("6 ... 7"), + FOO..=11 => println!("0 ... 11"), + _ => (), + } + + match 42 { + 2 => println!("2"), + 0..=5 => println!("0 ... 5"), + _ => (), + } + + match 42 { + 2 => println!("2"), + 0..=2 => println!("0 ... 2"), + _ => (), + } + + match 42 { + 0..=10 => println!("0 ... 10"), + 11..=50 => println!("11 ... 50"), + _ => (), + } + + match 42 { + 2 => println!("2"), + 0..2 => println!("0 .. 2"), + _ => (), + } + + match 42 { + 0..10 => println!("0 .. 10"), + 10..50 => println!("10 .. 50"), + _ => (), + } + + match 42 { + 0..11 => println!("0 .. 11"), + 0..=11 => println!("0 ... 11"), + _ => (), + } + + /* + // FIXME(JohnTitor): uncomment this once rustfmt knows half-open patterns + match 42 { + 0.. => println!("0 .. 42"), + 3.. => println!("3 .. 42"), + _ => (), + } + + match 42 { + ..=23 => println!("0 ... 23"), + ..26 => println!("0 .. 26"), + _ => (), + } + */ + + if let None = Some(42) { + // nothing + } else if let None = Some(42) { + // another nothing :-) + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_overlapping_arm.stderr b/src/tools/clippy/tests/ui/match_overlapping_arm.stderr new file mode 100644 index 0000000000000..eb20d5405a95e --- /dev/null +++ b/src/tools/clippy/tests/ui/match_overlapping_arm.stderr @@ -0,0 +1,63 @@ +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:12:9 + | +LL | 0..=10 => println!("0 ... 10"), + | ^^^^^^ + | + = note: `-D clippy::match-overlapping-arm` implied by `-D warnings` +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:13:9 + | +LL | 0..=11 => println!("0 ... 11"), + | ^^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:18:9 + | +LL | 0..=5 => println!("0 ... 5"), + | ^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:20:9 + | +LL | FOO..=11 => println!("0 ... 11"), + | ^^^^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:26:9 + | +LL | 0..=5 => println!("0 ... 5"), + | ^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:25:9 + | +LL | 2 => println!("2"), + | ^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:32:9 + | +LL | 0..=2 => println!("0 ... 2"), + | ^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:31:9 + | +LL | 2 => println!("2"), + | ^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:55:9 + | +LL | 0..11 => println!("0 .. 11"), + | ^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:56:9 + | +LL | 0..=11 => println!("0 ... 11"), + | ^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/match_ref_pats.rs b/src/tools/clippy/tests/ui/match_ref_pats.rs new file mode 100644 index 0000000000000..5de43733ad336 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_ref_pats.rs @@ -0,0 +1,74 @@ +#![warn(clippy::match_ref_pats)] + +fn ref_pats() { + { + let v = &Some(0); + match v { + &Some(v) => println!("{:?}", v), + &None => println!("none"), + } + match v { + // This doesn't trigger; we have a different pattern. + &Some(v) => println!("some"), + other => println!("other"), + } + } + let tup = &(1, 2); + match tup { + &(v, 1) => println!("{}", v), + _ => println!("none"), + } + // Special case: using `&` both in expr and pats. + let w = Some(0); + match &w { + &Some(v) => println!("{:?}", v), + &None => println!("none"), + } + // False positive: only wildcard pattern. + let w = Some(0); + #[allow(clippy::match_single_binding)] + match w { + _ => println!("none"), + } + + let a = &Some(0); + if let &None = a { + println!("none"); + } + + let b = Some(0); + if let &None = &b { + println!("none"); + } +} + +mod ice_3719 { + macro_rules! foo_variant( + ($idx:expr) => (Foo::get($idx).unwrap()) + ); + + enum Foo { + A, + B, + } + + impl Foo { + fn get(idx: u8) -> Option<&'static Self> { + match idx { + 0 => Some(&Foo::A), + 1 => Some(&Foo::B), + _ => None, + } + } + } + + fn ice_3719() { + // ICE #3719 + match foo_variant!(0) { + &Foo::A => println!("A"), + _ => println!("Wild"), + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_ref_pats.stderr b/src/tools/clippy/tests/ui/match_ref_pats.stderr new file mode 100644 index 0000000000000..52cb4a14b72bc --- /dev/null +++ b/src/tools/clippy/tests/ui/match_ref_pats.stderr @@ -0,0 +1,91 @@ +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:6:9 + | +LL | / match v { +LL | | &Some(v) => println!("{:?}", v), +LL | | &None => println!("none"), +LL | | } + | |_________^ + | + = note: `-D clippy::match-ref-pats` implied by `-D warnings` +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | match *v { +LL | Some(v) => println!("{:?}", v), +LL | None => println!("none"), + | + +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:17:5 + | +LL | / match tup { +LL | | &(v, 1) => println!("{}", v), +LL | | _ => println!("none"), +LL | | } + | |_____^ + | +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | match *tup { +LL | (v, 1) => println!("{}", v), + | + +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/match_ref_pats.rs:23:5 + | +LL | / match &w { +LL | | &Some(v) => println!("{:?}", v), +LL | | &None => println!("none"), +LL | | } + | |_____^ + | +help: try + | +LL | match w { +LL | Some(v) => println!("{:?}", v), +LL | None => println!("none"), + | + +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:35:5 + | +LL | / if let &None = a { +LL | | println!("none"); +LL | | } + | |_____^ + | +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | if let None = *a { + | ^^^^ ^^ + +error: you don't need to add `&` to both the expression and the patterns + --> $DIR/match_ref_pats.rs:40:5 + | +LL | / if let &None = &b { +LL | | println!("none"); +LL | | } + | |_____^ + | +help: try + | +LL | if let None = b { + | ^^^^ ^ + +error: you don't need to add `&` to all patterns + --> $DIR/match_ref_pats.rs:67:9 + | +LL | / match foo_variant!(0) { +LL | | &Foo::A => println!("A"), +LL | | _ => println!("Wild"), +LL | | } + | |_________^ + | +help: instead of prefixing all patterns with `&`, you can dereference the expression + | +LL | match *foo_variant!(0) { +LL | Foo::A => println!("A"), + | + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/match_same_arms.rs b/src/tools/clippy/tests/ui/match_same_arms.rs new file mode 100644 index 0000000000000..0b9342c9c4234 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms.rs @@ -0,0 +1,56 @@ +#![warn(clippy::match_same_arms)] + +pub enum Abc { + A, + B, + C, +} + +fn match_same_arms() { + let _ = match Abc::A { + Abc::A => 0, + Abc::B => 1, + _ => 0, //~ ERROR match arms have same body + }; + + match (1, 2, 3) { + (1, .., 3) => 42, + (.., 3) => 42, //~ ERROR match arms have same body + _ => 0, + }; + + let _ = match 42 { + 42 => 1, + 51 => 1, //~ ERROR match arms have same body + 41 => 2, + 52 => 2, //~ ERROR match arms have same body + _ => 0, + }; + + let _ = match 42 { + 1 => 2, + 2 => 2, //~ ERROR 2nd matched arms have same body + 3 => 2, //~ ERROR 3rd matched arms have same body + 4 => 3, + _ => 0, + }; +} + +mod issue4244 { + #[derive(PartialEq, PartialOrd, Eq, Ord)] + pub enum CommandInfo { + BuiltIn { name: String, about: Option }, + External { name: String, path: std::path::PathBuf }, + } + + impl CommandInfo { + pub fn name(&self) -> String { + match self { + CommandInfo::BuiltIn { name, .. } => name.to_string(), + CommandInfo::External { name, .. } => name.to_string(), + } + } + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_same_arms.stderr b/src/tools/clippy/tests/ui/match_same_arms.stderr new file mode 100644 index 0000000000000..0549886a1e8ec --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms.stderr @@ -0,0 +1,139 @@ +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:13:14 + | +LL | _ => 0, //~ ERROR match arms have same body + | ^ + | + = note: `-D clippy::match-same-arms` implied by `-D warnings` +note: same as this + --> $DIR/match_same_arms.rs:11:19 + | +LL | Abc::A => 0, + | ^ +note: `Abc::A` has the same arm body as the `_` wildcard, consider removing it + --> $DIR/match_same_arms.rs:11:19 + | +LL | Abc::A => 0, + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:18:20 + | +LL | (.., 3) => 42, //~ ERROR match arms have same body + | ^^ + | +note: same as this + --> $DIR/match_same_arms.rs:17:23 + | +LL | (1, .., 3) => 42, + | ^^ +help: consider refactoring into `(1, .., 3) | (.., 3)` + --> $DIR/match_same_arms.rs:17:9 + | +LL | (1, .., 3) => 42, + | ^^^^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:24:15 + | +LL | 51 => 1, //~ ERROR match arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:23:15 + | +LL | 42 => 1, + | ^ +help: consider refactoring into `42 | 51` + --> $DIR/match_same_arms.rs:23:9 + | +LL | 42 => 1, + | ^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:26:15 + | +LL | 52 => 2, //~ ERROR match arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:25:15 + | +LL | 41 => 2, + | ^ +help: consider refactoring into `41 | 52` + --> $DIR/match_same_arms.rs:25:9 + | +LL | 41 => 2, + | ^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:32:14 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:31:14 + | +LL | 1 => 2, + | ^ +help: consider refactoring into `1 | 2` + --> $DIR/match_same_arms.rs:31:9 + | +LL | 1 => 2, + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:33:14 + | +LL | 3 => 2, //~ ERROR 3rd matched arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:31:14 + | +LL | 1 => 2, + | ^ +help: consider refactoring into `1 | 3` + --> $DIR/match_same_arms.rs:31:9 + | +LL | 1 => 2, + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:33:14 + | +LL | 3 => 2, //~ ERROR 3rd matched arms have same body + | ^ + | +note: same as this + --> $DIR/match_same_arms.rs:32:14 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | ^ +help: consider refactoring into `2 | 3` + --> $DIR/match_same_arms.rs:32:9 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | ^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms.rs:50:55 + | +LL | CommandInfo::External { name, .. } => name.to_string(), + | ^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/match_same_arms.rs:49:54 + | +LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), + | ^^^^^^^^^^^^^^^^ +help: consider refactoring into `CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. }` + --> $DIR/match_same_arms.rs:49:17 + | +LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/match_same_arms2.rs b/src/tools/clippy/tests/ui/match_same_arms2.rs new file mode 100644 index 0000000000000..e1401d2796a52 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms2.rs @@ -0,0 +1,124 @@ +#![warn(clippy::match_same_arms)] +#![allow(clippy::blacklisted_name)] + +fn bar(_: T) {} +fn foo() -> bool { + unimplemented!() +} + +fn match_same_arms() { + let _ = match 42 { + 42 => { + foo(); + let mut a = 42 + [23].len() as i32; + if true { + a += 7; + } + a = -31 - a; + a + }, + _ => { + //~ ERROR match arms have same body + foo(); + let mut a = 42 + [23].len() as i32; + if true { + a += 7; + } + a = -31 - a; + a + }, + }; + + let _ = match 42 { + 42 => foo(), + 51 => foo(), //~ ERROR match arms have same body + _ => true, + }; + + let _ = match Some(42) { + Some(_) => 24, + None => 24, //~ ERROR match arms have same body + }; + + let _ = match Some(42) { + Some(foo) => 24, + None => 24, + }; + + let _ = match Some(42) { + Some(42) => 24, + Some(a) => 24, // bindings are different + None => 0, + }; + + let _ = match Some(42) { + Some(a) if a > 0 => 24, + Some(a) => 24, // one arm has a guard + None => 0, + }; + + match (Some(42), Some(42)) { + (Some(a), None) => bar(a), + (None, Some(a)) => bar(a), //~ ERROR match arms have same body + _ => (), + } + + match (Some(42), Some(42)) { + (Some(a), ..) => bar(a), + (.., Some(a)) => bar(a), //~ ERROR match arms have same body + _ => (), + } + + let _ = match Some(()) { + Some(()) => 0.0, + None => -0.0, + }; + + match (Some(42), Some("")) { + (Some(a), None) => bar(a), + (None, Some(a)) => bar(a), // bindings have different types + _ => (), + } + + let x: Result = Ok(3); + + // No warning because of the guard. + match x { + Ok(x) if x * x == 64 => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + // This used to be a false positive; see issue #1996. + match x { + Ok(3) => println!("ok"), + Ok(x) if x * x == 64 => println!("ok 64"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + match (x, Some(1i32)) { + (Ok(x), Some(_)) => println!("ok {}", x), + (Ok(_), Some(x)) => println!("ok {}", x), + _ => println!("err"), + } + + // No warning; different types for `x`. + match (x, Some(1.0f64)) { + (Ok(x), Some(_)) => println!("ok {}", x), + (Ok(_), Some(x)) => println!("ok {}", x), + _ => println!("err"), + } + + // False negative #2251. + match x { + Ok(_tmp) => println!("ok"), + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => { + unreachable!(); + }, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_same_arms2.stderr b/src/tools/clippy/tests/ui/match_same_arms2.stderr new file mode 100644 index 0000000000000..26c65f32ad780 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_same_arms2.stderr @@ -0,0 +1,145 @@ +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:20:14 + | +LL | _ => { + | ______________^ +LL | | //~ ERROR match arms have same body +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +... | +LL | | a +LL | | }, + | |_________^ + | + = note: `-D clippy::match-same-arms` implied by `-D warnings` +note: same as this + --> $DIR/match_same_arms2.rs:11:15 + | +LL | 42 => { + | _______________^ +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +LL | | if true { +... | +LL | | a +LL | | }, + | |_________^ +note: `42` has the same arm body as the `_` wildcard, consider removing it + --> $DIR/match_same_arms2.rs:11:15 + | +LL | 42 => { + | _______________^ +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +LL | | if true { +... | +LL | | a +LL | | }, + | |_________^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:34:15 + | +LL | 51 => foo(), //~ ERROR match arms have same body + | ^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:33:15 + | +LL | 42 => foo(), + | ^^^^^ +help: consider refactoring into `42 | 51` + --> $DIR/match_same_arms2.rs:33:9 + | +LL | 42 => foo(), + | ^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:40:17 + | +LL | None => 24, //~ ERROR match arms have same body + | ^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:39:20 + | +LL | Some(_) => 24, + | ^^ +help: consider refactoring into `Some(_) | None` + --> $DIR/match_same_arms2.rs:39:9 + | +LL | Some(_) => 24, + | ^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:62:28 + | +LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:61:28 + | +LL | (Some(a), None) => bar(a), + | ^^^^^^ +help: consider refactoring into `(Some(a), None) | (None, Some(a))` + --> $DIR/match_same_arms2.rs:61:9 + | +LL | (Some(a), None) => bar(a), + | ^^^^^^^^^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:68:26 + | +LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:67:26 + | +LL | (Some(a), ..) => bar(a), + | ^^^^^^ +help: consider refactoring into `(Some(a), ..) | (.., Some(a))` + --> $DIR/match_same_arms2.rs:67:9 + | +LL | (Some(a), ..) => bar(a), + | ^^^^^^^^^^^^^ + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:102:29 + | +LL | (Ok(_), Some(x)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:101:29 + | +LL | (Ok(x), Some(_)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^^^^^ +help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))` + --> $DIR/match_same_arms2.rs:101:9 + | +LL | (Ok(x), Some(_)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this `match` has identical arm bodies + --> $DIR/match_same_arms2.rs:117:18 + | +LL | Ok(_) => println!("ok"), + | ^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/match_same_arms2.rs:116:18 + | +LL | Ok(3) => println!("ok"), + | ^^^^^^^^^^^^^^ +help: consider refactoring into `Ok(3) | Ok(_)` + --> $DIR/match_same_arms2.rs:116:9 + | +LL | Ok(3) => println!("ok"), + | ^^^^^ + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/match_single_binding.fixed b/src/tools/clippy/tests/ui/match_single_binding.fixed new file mode 100644 index 0000000000000..f3627902eec98 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_single_binding.fixed @@ -0,0 +1,90 @@ +// run-rustfix + +#![warn(clippy::match_single_binding)] +#![allow(unused_variables, clippy::many_single_char_names, clippy::toplevel_ref_arg)] + +struct Point { + x: i32, + y: i32, +} + +fn coords() -> Point { + Point { x: 1, y: 2 } +} + +macro_rules! foo { + ($param:expr) => { + match $param { + _ => println!("whatever"), + } + }; +} + +fn main() { + let a = 1; + let b = 2; + let c = 3; + // Lint + let (x, y, z) = (a, b, c); + { + println!("{} {} {}", x, y, z); + } + // Lint + let (x, y, z) = (a, b, c); + println!("{} {} {}", x, y, z); + // Ok + foo!(a); + // Ok + match a { + 2 => println!("2"), + _ => println!("Not 2"), + } + // Ok + let d = Some(5); + match d { + Some(d) => println!("{}", d), + _ => println!("None"), + } + // Lint + println!("whatever"); + // Lint + { + let x = 29; + println!("x has a value of {}", x); + } + // Lint + { + let e = 5 * a; + if e >= 5 { + println!("e is superior to 5"); + } + } + // Lint + let p = Point { x: 0, y: 7 }; + let Point { x, y } = p; + println!("Coords: ({}, {})", x, y); + // Lint + let Point { x: x1, y: y1 } = p; + println!("Coords: ({}, {})", x1, y1); + // Lint + let x = 5; + let ref r = x; + println!("Got a reference to {}", r); + // Lint + let mut x = 5; + let ref mut mr = x; + println!("Got a mutable reference to {}", mr); + // Lint + let Point { x, y } = coords(); + let product = x * y; + // Lint + let v = vec![Some(1), Some(2), Some(3), Some(4)]; + #[allow(clippy::let_and_return)] + let _ = v + .iter() + .map(|i| { + let unwrapped = i.unwrap(); + unwrapped + }) + .collect::>(); +} diff --git a/src/tools/clippy/tests/ui/match_single_binding.rs b/src/tools/clippy/tests/ui/match_single_binding.rs new file mode 100644 index 0000000000000..8c182148ae184 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_single_binding.rs @@ -0,0 +1,102 @@ +// run-rustfix + +#![warn(clippy::match_single_binding)] +#![allow(unused_variables, clippy::many_single_char_names, clippy::toplevel_ref_arg)] + +struct Point { + x: i32, + y: i32, +} + +fn coords() -> Point { + Point { x: 1, y: 2 } +} + +macro_rules! foo { + ($param:expr) => { + match $param { + _ => println!("whatever"), + } + }; +} + +fn main() { + let a = 1; + let b = 2; + let c = 3; + // Lint + match (a, b, c) { + (x, y, z) => { + println!("{} {} {}", x, y, z); + }, + } + // Lint + match (a, b, c) { + (x, y, z) => println!("{} {} {}", x, y, z), + } + // Ok + foo!(a); + // Ok + match a { + 2 => println!("2"), + _ => println!("Not 2"), + } + // Ok + let d = Some(5); + match d { + Some(d) => println!("{}", d), + _ => println!("None"), + } + // Lint + match a { + _ => println!("whatever"), + } + // Lint + match a { + _ => { + let x = 29; + println!("x has a value of {}", x); + }, + } + // Lint + match a { + _ => { + let e = 5 * a; + if e >= 5 { + println!("e is superior to 5"); + } + }, + } + // Lint + let p = Point { x: 0, y: 7 }; + match p { + Point { x, y } => println!("Coords: ({}, {})", x, y), + } + // Lint + match p { + Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), + } + // Lint + let x = 5; + match x { + ref r => println!("Got a reference to {}", r), + } + // Lint + let mut x = 5; + match x { + ref mut mr => println!("Got a mutable reference to {}", mr), + } + // Lint + let product = match coords() { + Point { x, y } => x * y, + }; + // Lint + let v = vec![Some(1), Some(2), Some(3), Some(4)]; + #[allow(clippy::let_and_return)] + let _ = v + .iter() + .map(|i| match i.unwrap() { + unwrapped => unwrapped, + }) + .collect::>(); +} diff --git a/src/tools/clippy/tests/ui/match_single_binding.stderr b/src/tools/clippy/tests/ui/match_single_binding.stderr new file mode 100644 index 0000000000000..795c8c3e24d7e --- /dev/null +++ b/src/tools/clippy/tests/ui/match_single_binding.stderr @@ -0,0 +1,171 @@ +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:28:5 + | +LL | / match (a, b, c) { +LL | | (x, y, z) => { +LL | | println!("{} {} {}", x, y, z); +LL | | }, +LL | | } + | |_____^ + | + = note: `-D clippy::match-single-binding` implied by `-D warnings` +help: consider using `let` statement + | +LL | let (x, y, z) = (a, b, c); +LL | { +LL | println!("{} {} {}", x, y, z); +LL | } + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:34:5 + | +LL | / match (a, b, c) { +LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let (x, y, z) = (a, b, c); +LL | println!("{} {} {}", x, y, z); + | + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:51:5 + | +LL | / match a { +LL | | _ => println!("whatever"), +LL | | } + | |_____^ help: consider using the match body instead: `println!("whatever");` + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:55:5 + | +LL | / match a { +LL | | _ => { +LL | | let x = 29; +LL | | println!("x has a value of {}", x); +LL | | }, +LL | | } + | |_____^ + | +help: consider using the match body instead + | +LL | { +LL | let x = 29; +LL | println!("x has a value of {}", x); +LL | } + | + +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:62:5 + | +LL | / match a { +LL | | _ => { +LL | | let e = 5 * a; +LL | | if e >= 5 { +... | +LL | | }, +LL | | } + | |_____^ + | +help: consider using the match body instead + | +LL | { +LL | let e = 5 * a; +LL | if e >= 5 { +LL | println!("e is superior to 5"); +LL | } +LL | } + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:72:5 + | +LL | / match p { +LL | | Point { x, y } => println!("Coords: ({}, {})", x, y), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let Point { x, y } = p; +LL | println!("Coords: ({}, {})", x, y); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:76:5 + | +LL | / match p { +LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let Point { x: x1, y: y1 } = p; +LL | println!("Coords: ({}, {})", x1, y1); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:81:5 + | +LL | / match x { +LL | | ref r => println!("Got a reference to {}", r), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let ref r = x; +LL | println!("Got a reference to {}", r); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:86:5 + | +LL | / match x { +LL | | ref mut mr => println!("Got a mutable reference to {}", mr), +LL | | } + | |_____^ + | +help: consider using `let` statement + | +LL | let ref mut mr = x; +LL | println!("Got a mutable reference to {}", mr); + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:90:5 + | +LL | / let product = match coords() { +LL | | Point { x, y } => x * y, +LL | | }; + | |______^ + | +help: consider using `let` statement + | +LL | let Point { x, y } = coords(); +LL | let product = x * y; + | + +error: this match could be written as a `let` statement + --> $DIR/match_single_binding.rs:98:18 + | +LL | .map(|i| match i.unwrap() { + | __________________^ +LL | | unwrapped => unwrapped, +LL | | }) + | |_________^ + | +help: consider using `let` statement + | +LL | .map(|i| { +LL | let unwrapped = i.unwrap(); +LL | unwrapped +LL | }) + | + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/match_wild_err_arm.rs b/src/tools/clippy/tests/ui/match_wild_err_arm.rs new file mode 100644 index 0000000000000..823be65efe065 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wild_err_arm.rs @@ -0,0 +1,65 @@ +#![feature(exclusive_range_pattern)] +#![allow(clippy::match_same_arms)] +#![warn(clippy::match_wild_err_arm)] + +fn match_wild_err_arm() { + let x: Result = Ok(3); + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => panic!("err"), + } + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => panic!(), + } + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => { + panic!(); + }, + } + + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_e) => panic!(), + } + + // Allowed when used in `panic!`. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_e) => panic!("{}", _e), + } + + // Allowed when not with `panic!` block. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + // Allowed when used with `unreachable!`. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => unreachable!(), + } + + // Allowed when used with `unreachable!`. + match x { + Ok(3) => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => { + unreachable!(); + }, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/match_wild_err_arm.stderr b/src/tools/clippy/tests/ui/match_wild_err_arm.stderr new file mode 100644 index 0000000000000..20d4c933418b7 --- /dev/null +++ b/src/tools/clippy/tests/ui/match_wild_err_arm.stderr @@ -0,0 +1,35 @@ +error: `Err(_)` matches all errors + --> $DIR/match_wild_err_arm.rs:11:9 + | +LL | Err(_) => panic!("err"), + | ^^^^^^ + | + = note: `-D clippy::match-wild-err-arm` implied by `-D warnings` + = note: match each error separately or use the error output + +error: `Err(_)` matches all errors + --> $DIR/match_wild_err_arm.rs:17:9 + | +LL | Err(_) => panic!(), + | ^^^^^^ + | + = note: match each error separately or use the error output + +error: `Err(_)` matches all errors + --> $DIR/match_wild_err_arm.rs:23:9 + | +LL | Err(_) => { + | ^^^^^^ + | + = note: match each error separately or use the error output + +error: `Err(_e)` matches all errors + --> $DIR/match_wild_err_arm.rs:31:9 + | +LL | Err(_e) => panic!(), + | ^^^^^^^ + | + = note: match each error separately or use the error output + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_discriminant.fixed b/src/tools/clippy/tests/ui/mem_discriminant.fixed new file mode 100644 index 0000000000000..69a8f286d050d --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#![deny(clippy::mem_discriminant_non_enum)] + +use std::mem; + +enum Foo { + One(usize), + Two(u8), +} + +fn main() { + // bad + mem::discriminant(&Some(2)); + mem::discriminant(&None::); + mem::discriminant(&Foo::One(5)); + mem::discriminant(&Foo::Two(5)); + + let ro = &Some(3); + let rro = &ro; + mem::discriminant(ro); + mem::discriminant(*rro); + mem::discriminant(*rro); + + macro_rules! mem_discriminant_but_in_a_macro { + ($param:expr) => { + mem::discriminant($param) + }; + } + + mem_discriminant_but_in_a_macro!(*rro); + + let rrrrro = &&&rro; + mem::discriminant(****rrrrro); + mem::discriminant(****rrrrro); + + // ok + mem::discriminant(&Some(2)); + mem::discriminant(&None::); + mem::discriminant(&Foo::One(5)); + mem::discriminant(&Foo::Two(5)); + mem::discriminant(ro); + mem::discriminant(*rro); + mem::discriminant(****rrrrro); +} diff --git a/src/tools/clippy/tests/ui/mem_discriminant.rs b/src/tools/clippy/tests/ui/mem_discriminant.rs new file mode 100644 index 0000000000000..55db50fcdc733 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#![deny(clippy::mem_discriminant_non_enum)] + +use std::mem; + +enum Foo { + One(usize), + Two(u8), +} + +fn main() { + // bad + mem::discriminant(&&Some(2)); + mem::discriminant(&&None::); + mem::discriminant(&&Foo::One(5)); + mem::discriminant(&&Foo::Two(5)); + + let ro = &Some(3); + let rro = &ro; + mem::discriminant(&ro); + mem::discriminant(rro); + mem::discriminant(&rro); + + macro_rules! mem_discriminant_but_in_a_macro { + ($param:expr) => { + mem::discriminant($param) + }; + } + + mem_discriminant_but_in_a_macro!(&rro); + + let rrrrro = &&&rro; + mem::discriminant(&rrrrro); + mem::discriminant(*rrrrro); + + // ok + mem::discriminant(&Some(2)); + mem::discriminant(&None::); + mem::discriminant(&Foo::One(5)); + mem::discriminant(&Foo::Two(5)); + mem::discriminant(ro); + mem::discriminant(*rro); + mem::discriminant(****rrrrro); +} diff --git a/src/tools/clippy/tests/ui/mem_discriminant.stderr b/src/tools/clippy/tests/ui/mem_discriminant.stderr new file mode 100644 index 0000000000000..8d9810970adee --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant.stderr @@ -0,0 +1,94 @@ +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:14:5 + | +LL | mem::discriminant(&&Some(2)); + | ^^^^^^^^^^^^^^^^^^---------^ + | | + | help: try dereferencing: `&Some(2)` + | +note: the lint level is defined here + --> $DIR/mem_discriminant.rs:3:9 + | +LL | #![deny(clippy::mem_discriminant_non_enum)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:15:5 + | +LL | mem::discriminant(&&None::); + | ^^^^^^^^^^^^^^^^^^------------^ + | | + | help: try dereferencing: `&None::` + +error: calling `mem::discriminant` on non-enum type `&Foo` + --> $DIR/mem_discriminant.rs:16:5 + | +LL | mem::discriminant(&&Foo::One(5)); + | ^^^^^^^^^^^^^^^^^^-------------^ + | | + | help: try dereferencing: `&Foo::One(5)` + +error: calling `mem::discriminant` on non-enum type `&Foo` + --> $DIR/mem_discriminant.rs:17:5 + | +LL | mem::discriminant(&&Foo::Two(5)); + | ^^^^^^^^^^^^^^^^^^-------------^ + | | + | help: try dereferencing: `&Foo::Two(5)` + +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:21:5 + | +LL | mem::discriminant(&ro); + | ^^^^^^^^^^^^^^^^^^---^ + | | + | help: try dereferencing: `ro` + +error: calling `mem::discriminant` on non-enum type `&std::option::Option` + --> $DIR/mem_discriminant.rs:22:5 + | +LL | mem::discriminant(rro); + | ^^^^^^^^^^^^^^^^^^---^ + | | + | help: try dereferencing: `*rro` + +error: calling `mem::discriminant` on non-enum type `&&std::option::Option` + --> $DIR/mem_discriminant.rs:23:5 + | +LL | mem::discriminant(&rro); + | ^^^^^^^^^^^^^^^^^^----^ + | | + | help: try dereferencing: `*rro` + +error: calling `mem::discriminant` on non-enum type `&&std::option::Option` + --> $DIR/mem_discriminant.rs:27:13 + | +LL | mem::discriminant($param) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mem_discriminant_but_in_a_macro!(&rro); + | --------------------------------------- + | | | + | | help: try dereferencing: `*rro` + | in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: calling `mem::discriminant` on non-enum type `&&&&&std::option::Option` + --> $DIR/mem_discriminant.rs:34:5 + | +LL | mem::discriminant(&rrrrro); + | ^^^^^^^^^^^^^^^^^^-------^ + | | + | help: try dereferencing: `****rrrrro` + +error: calling `mem::discriminant` on non-enum type `&&&std::option::Option` + --> $DIR/mem_discriminant.rs:35:5 + | +LL | mem::discriminant(*rrrrro); + | ^^^^^^^^^^^^^^^^^^-------^ + | | + | help: try dereferencing: `****rrrrro` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_discriminant_unfixable.rs b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.rs new file mode 100644 index 0000000000000..e245d3257d55d --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.rs @@ -0,0 +1,16 @@ +#![deny(clippy::mem_discriminant_non_enum)] + +use std::mem; + +enum Foo { + One(usize), + Two(u8), +} + +struct A(Foo); + +fn main() { + // bad + mem::discriminant(&"hello"); + mem::discriminant(&A(Foo::One(0))); +} diff --git a/src/tools/clippy/tests/ui/mem_discriminant_unfixable.stderr b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.stderr new file mode 100644 index 0000000000000..e2de3776f2c91 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_discriminant_unfixable.stderr @@ -0,0 +1,20 @@ +error: calling `mem::discriminant` on non-enum type `&str` + --> $DIR/mem_discriminant_unfixable.rs:14:5 + | +LL | mem::discriminant(&"hello"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/mem_discriminant_unfixable.rs:1:9 + | +LL | #![deny(clippy::mem_discriminant_non_enum)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: calling `mem::discriminant` on non-enum type `A` + --> $DIR/mem_discriminant_unfixable.rs:15:5 + | +LL | mem::discriminant(&A(Foo::One(0))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_forget.rs b/src/tools/clippy/tests/ui/mem_forget.rs new file mode 100644 index 0000000000000..e5b35c098a239 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_forget.rs @@ -0,0 +1,23 @@ +use std::rc::Rc; +use std::sync::Arc; + +use std::mem as memstuff; +use std::mem::forget as forgetSomething; + +#[warn(clippy::mem_forget)] +#[allow(clippy::forget_copy)] +fn main() { + let five: i32 = 5; + forgetSomething(five); + + let six: Arc = Arc::new(6); + memstuff::forget(six); + + let seven: Rc = Rc::new(7); + std::mem::forget(seven); + + let eight: Vec = vec![8]; + forgetSomething(eight); + + std::mem::forget(7); +} diff --git a/src/tools/clippy/tests/ui/mem_forget.stderr b/src/tools/clippy/tests/ui/mem_forget.stderr new file mode 100644 index 0000000000000..a90d8b1655dc4 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_forget.stderr @@ -0,0 +1,22 @@ +error: usage of `mem::forget` on `Drop` type + --> $DIR/mem_forget.rs:14:5 + | +LL | memstuff::forget(six); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mem-forget` implied by `-D warnings` + +error: usage of `mem::forget` on `Drop` type + --> $DIR/mem_forget.rs:17:5 + | +LL | std::mem::forget(seven); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: usage of `mem::forget` on `Drop` type + --> $DIR/mem_forget.rs:20:5 + | +LL | forgetSomething(eight); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_replace.fixed b/src/tools/clippy/tests/ui/mem_replace.fixed new file mode 100644 index 0000000000000..54e962e7116e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace.fixed @@ -0,0 +1,30 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn( + clippy::all, + clippy::style, + clippy::mem_replace_option_with_none, + clippy::mem_replace_with_default +)] + +use std::mem; + +fn replace_option_with_none() { + let mut an_option = Some(1); + let _ = an_option.take(); + let an_option = &mut Some(1); + let _ = an_option.take(); +} + +fn replace_with_default() { + let mut s = String::from("foo"); + let _ = std::mem::take(&mut s); + let s = &mut String::from("foo"); + let _ = std::mem::take(s); + let _ = std::mem::take(s); +} + +fn main() { + replace_option_with_none(); + replace_with_default(); +} diff --git a/src/tools/clippy/tests/ui/mem_replace.rs b/src/tools/clippy/tests/ui/mem_replace.rs new file mode 100644 index 0000000000000..60f527810716f --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace.rs @@ -0,0 +1,30 @@ +// run-rustfix +#![allow(unused_imports)] +#![warn( + clippy::all, + clippy::style, + clippy::mem_replace_option_with_none, + clippy::mem_replace_with_default +)] + +use std::mem; + +fn replace_option_with_none() { + let mut an_option = Some(1); + let _ = mem::replace(&mut an_option, None); + let an_option = &mut Some(1); + let _ = mem::replace(an_option, None); +} + +fn replace_with_default() { + let mut s = String::from("foo"); + let _ = std::mem::replace(&mut s, String::default()); + let s = &mut String::from("foo"); + let _ = std::mem::replace(s, String::default()); + let _ = std::mem::replace(s, Default::default()); +} + +fn main() { + replace_option_with_none(); + replace_with_default(); +} diff --git a/src/tools/clippy/tests/ui/mem_replace.stderr b/src/tools/clippy/tests/ui/mem_replace.stderr new file mode 100644 index 0000000000000..245d33aa4f260 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace.stderr @@ -0,0 +1,36 @@ +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:14:13 + | +LL | let _ = mem::replace(&mut an_option, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `an_option.take()` + | + = note: `-D clippy::mem-replace-option-with-none` implied by `-D warnings` + +error: replacing an `Option` with `None` + --> $DIR/mem_replace.rs:16:13 + | +LL | let _ = mem::replace(an_option, None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::take()` instead: `an_option.take()` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:21:13 + | +LL | let _ = std::mem::replace(&mut s, String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(&mut s)` + | + = note: `-D clippy::mem-replace-with-default` implied by `-D warnings` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:23:13 + | +LL | let _ = std::mem::replace(s, String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(s)` + +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace.rs:24:13 + | +LL | let _ = std::mem::replace(s, Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(s)` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mem_replace_macro.rs b/src/tools/clippy/tests/ui/mem_replace_macro.rs new file mode 100644 index 0000000000000..0c09344b80d10 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace_macro.rs @@ -0,0 +1,21 @@ +// aux-build:macro_rules.rs +#![warn(clippy::mem_replace_with_default)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! take { + ($s:expr) => { + std::mem::replace($s, Default::default()) + }; +} + +fn replace_with_default() { + let s = &mut String::from("foo"); + take!(s); + take_external!(s); +} + +fn main() { + replace_with_default(); +} diff --git a/src/tools/clippy/tests/ui/mem_replace_macro.stderr b/src/tools/clippy/tests/ui/mem_replace_macro.stderr new file mode 100644 index 0000000000000..4971a91050bf2 --- /dev/null +++ b/src/tools/clippy/tests/ui/mem_replace_macro.stderr @@ -0,0 +1,14 @@ +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> $DIR/mem_replace_macro.rs:9:9 + | +LL | std::mem::replace($s, Default::default()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | take!(s); + | --------- in this macro invocation + | + = note: `-D clippy::mem-replace-with-default` implied by `-D warnings` + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/methods.rs b/src/tools/clippy/tests/ui/methods.rs new file mode 100644 index 0000000000000..7880cf36415ff --- /dev/null +++ b/src/tools/clippy/tests/ui/methods.rs @@ -0,0 +1,247 @@ +// aux-build:option_helpers.rs +// edition:2018 + +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::blacklisted_name, + clippy::default_trait_access, + clippy::missing_docs_in_private_items, + clippy::missing_safety_doc, + clippy::non_ascii_literal, + clippy::new_without_default, + clippy::needless_pass_by_value, + clippy::print_stdout, + clippy::must_use_candidate, + clippy::use_self, + clippy::useless_format, + clippy::wrong_self_convention, + clippy::unused_self, + unused +)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; +use std::iter::FromIterator; +use std::ops::Mul; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +use option_helpers::IteratorFalsePositives; + +pub struct T; + +impl T { + pub fn add(self, other: T) -> T { + self + } + + // no error, not public interface + pub(crate) fn drop(&mut self) {} + + // no error, private function + fn neg(self) -> Self { + self + } + + // no error, private function + fn eq(&self, other: T) -> bool { + true + } + + // No error; self is a ref. + fn sub(&self, other: T) -> &T { + self + } + + // No error; different number of arguments. + fn div(self) -> T { + self + } + + // No error; wrong return type. + fn rem(self, other: T) {} + + // Fine + fn into_u32(self) -> u32 { + 0 + } + + fn into_u16(&self) -> u16 { + 0 + } + + fn to_something(self) -> u32 { + 0 + } + + fn new(self) -> Self { + unimplemented!(); + } +} + +pub struct T1; + +impl T1 { + // Shouldn't trigger lint as it is unsafe. + pub unsafe fn add(self, rhs: T1) -> T1 { + self + } + + // Should not trigger lint since this is an async function. + pub async fn next(&mut self) -> Option { + None + } +} + +struct Lt<'a> { + foo: &'a u32, +} + +impl<'a> Lt<'a> { + // The lifetime is different, but that’s irrelevant; see issue #734. + #[allow(clippy::needless_lifetimes)] + pub fn new<'b>(s: &'b str) -> Lt<'b> { + unimplemented!() + } +} + +struct Lt2<'a> { + foo: &'a u32, +} + +impl<'a> Lt2<'a> { + // The lifetime is different, but that’s irrelevant; see issue #734. + pub fn new(s: &str) -> Lt2 { + unimplemented!() + } +} + +struct Lt3<'a> { + foo: &'a u32, +} + +impl<'a> Lt3<'a> { + // The lifetime is different, but that’s irrelevant; see issue #734. + pub fn new() -> Lt3<'static> { + unimplemented!() + } +} + +#[derive(Clone, Copy)] +struct U; + +impl U { + fn new() -> Self { + U + } + // Ok because `U` is `Copy`. + fn to_something(self) -> u32 { + 0 + } +} + +struct V { + _dummy: T, +} + +impl V { + fn new() -> Option> { + None + } +} + +struct AsyncNew; + +impl AsyncNew { + async fn new() -> Option { + None + } +} + +struct BadNew; + +impl BadNew { + fn new() -> i32 { + 0 + } +} + +impl Mul for T { + type Output = T; + // No error, obviously. + fn mul(self, other: T) -> T { + self + } +} + +/// Checks implementation of `FILTER_NEXT` lint. +#[rustfmt::skip] +fn filter_next() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Single-line case. + let _ = v.iter().filter(|&x| *x < 0).next(); + + // Multi-line case. + let _ = v.iter().filter(|&x| { + *x < 0 + } + ).next(); + + // Check that hat we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.filter().next(); +} + +/// Checks implementation of `SEARCH_IS_SOME` lint. +#[rustfmt::skip] +fn search_is_some() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + let y = &&42; + + // Check `find().is_some()`, single-line case. + let _ = v.iter().find(|&x| *x < 0).is_some(); + let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less + let _ = (0..1).find(|x| *x == 0).is_some(); + let _ = v.iter().find(|x| **x == 0).is_some(); + + // Check `find().is_some()`, multi-line case. + let _ = v.iter().find(|&x| { + *x < 0 + } + ).is_some(); + + // Check `position().is_some()`, single-line case. + let _ = v.iter().position(|&x| x < 0).is_some(); + + // Check `position().is_some()`, multi-line case. + let _ = v.iter().position(|&x| { + x < 0 + } + ).is_some(); + + // Check `rposition().is_some()`, single-line case. + let _ = v.iter().rposition(|&x| x < 0).is_some(); + + // Check `rposition().is_some()`, multi-line case. + let _ = v.iter().rposition(|&x| { + x < 0 + } + ).is_some(); + + // Check that we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.find().is_some(); + let _ = foo.position().is_some(); + let _ = foo.rposition().is_some(); +} + +fn main() { + filter_next(); + search_is_some(); +} diff --git a/src/tools/clippy/tests/ui/methods.stderr b/src/tools/clippy/tests/ui/methods.stderr new file mode 100644 index 0000000000000..01cf487ac148e --- /dev/null +++ b/src/tools/clippy/tests/ui/methods.stderr @@ -0,0 +1,109 @@ +error: defining a method called `add` on this type; consider implementing the `std::ops::Add` trait or choosing a less ambiguous name + --> $DIR/methods.rs:39:5 + | +LL | / pub fn add(self, other: T) -> T { +LL | | self +LL | | } + | |_____^ + | + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + +error: methods called `new` usually return `Self` + --> $DIR/methods.rs:169:5 + | +LL | / fn new() -> i32 { +LL | | 0 +LL | | } + | |_____^ + | + = note: `-D clippy::new-ret-no-self` implied by `-D warnings` + +error: called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(p)` instead. + --> $DIR/methods.rs:188:13 + | +LL | let _ = v.iter().filter(|&x| *x < 0).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::filter-next` implied by `-D warnings` + = note: replace `filter(|&x| *x < 0).next()` with `find(|&x| *x < 0)` + +error: called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(p)` instead. + --> $DIR/methods.rs:191:13 + | +LL | let _ = v.iter().filter(|&x| { + | _____________^ +LL | | *x < 0 +LL | | } +LL | | ).next(); + | |___________________________^ + +error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:208:22 + | +LL | let _ = v.iter().find(|&x| *x < 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x < 0)` + | + = note: `-D clippy::search-is-some` implied by `-D warnings` + +error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:209:20 + | +LL | let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| **y == x)` + +error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:210:20 + | +LL | let _ = (0..1).find(|x| *x == 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| x == 0)` + +error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:211:22 + | +LL | let _ = v.iter().find(|x| **x == 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x == 0)` + +error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:214:13 + | +LL | let _ = v.iter().find(|&x| { + | _____________^ +LL | | *x < 0 +LL | | } +LL | | ).is_some(); + | |______________________________^ + +error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:220:22 + | +LL | let _ = v.iter().position(|&x| x < 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)` + +error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:223:13 + | +LL | let _ = v.iter().position(|&x| { + | _____________^ +LL | | x < 0 +LL | | } +LL | | ).is_some(); + | |______________________________^ + +error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:229:22 + | +LL | let _ = v.iter().rposition(|&x| x < 0).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)` + +error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`. + --> $DIR/methods.rs:232:13 + | +LL | let _ = v.iter().rposition(|&x| { + | _____________^ +LL | | x < 0 +LL | | } +LL | | ).is_some(); + | |______________________________^ + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/min_max.rs b/src/tools/clippy/tests/ui/min_max.rs new file mode 100644 index 0000000000000..8307d4b3019f7 --- /dev/null +++ b/src/tools/clippy/tests/ui/min_max.rs @@ -0,0 +1,33 @@ +#![warn(clippy::all)] + +use std::cmp::max as my_max; +use std::cmp::min as my_min; +use std::cmp::{max, min}; + +const LARGE: usize = 3; + +fn main() { + let x; + x = 2usize; + min(1, max(3, x)); + min(max(3, x), 1); + max(min(x, 1), 3); + max(3, min(x, 1)); + + my_max(3, my_min(x, 1)); + + min(3, max(1, x)); // ok, could be 1, 2 or 3 depending on x + + min(1, max(LARGE, x)); // no error, we don't lookup consts here + + let y = 2isize; + min(max(y, -1), 3); + + let s; + s = "Hello"; + + min("Apple", max("Zoo", s)); + max(min(s, "Apple"), "Zoo"); + + max("Apple", min(s, "Zoo")); // ok +} diff --git a/src/tools/clippy/tests/ui/min_max.stderr b/src/tools/clippy/tests/ui/min_max.stderr new file mode 100644 index 0000000000000..b552c137f7c7c --- /dev/null +++ b/src/tools/clippy/tests/ui/min_max.stderr @@ -0,0 +1,46 @@ +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:12:5 + | +LL | min(1, max(3, x)); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::min-max` implied by `-D warnings` + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:13:5 + | +LL | min(max(3, x), 1); + | ^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:14:5 + | +LL | max(min(x, 1), 3); + | ^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:15:5 + | +LL | max(3, min(x, 1)); + | ^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:17:5 + | +LL | my_max(3, my_min(x, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:29:5 + | +LL | min("Apple", max("Zoo", s)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `min`/`max` combination leads to constant result + --> $DIR/min_max.rs:30:5 + | +LL | max(min(s, "Apple"), "Zoo"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.fixed b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.fixed new file mode 100644 index 0000000000000..3ee77dcac31a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.fixed @@ -0,0 +1,30 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(target_os = "cloudabi")] +fn cloudabi() {} + +#[cfg(target_os = "hermit")] +fn hermit() {} + +#[cfg(target_os = "wasi")] +fn wasi() {} + +#[cfg(target_os = "none")] +fn none() {} + +// list with conditions +#[cfg(all(not(any(windows, target_os = "cloudabi")), target_os = "wasi"))] +fn list() {} + +// windows is a valid target family, should be ignored +#[cfg(windows)] +fn windows() {} + +// correct use, should be ignored +#[cfg(target_os = "hermit")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.rs b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.rs new file mode 100644 index 0000000000000..9cc411418e4c4 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.rs @@ -0,0 +1,30 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(cloudabi)] +fn cloudabi() {} + +#[cfg(hermit)] +fn hermit() {} + +#[cfg(wasi)] +fn wasi() {} + +#[cfg(none)] +fn none() {} + +// list with conditions +#[cfg(all(not(any(windows, cloudabi)), wasi))] +fn list() {} + +// windows is a valid target family, should be ignored +#[cfg(windows)] +fn windows() {} + +// correct use, should be ignored +#[cfg(target_os = "hermit")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.stderr b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.stderr new file mode 100644 index 0000000000000..78fc27752d239 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_non_unix.stderr @@ -0,0 +1,51 @@ +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:6:1 + | +LL | #[cfg(cloudabi)] + | ^^^^^^--------^^ + | | + | help: try: `target_os = "cloudabi"` + | + = note: `-D clippy::mismatched-target-os` implied by `-D warnings` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:9:1 + | +LL | #[cfg(hermit)] + | ^^^^^^------^^ + | | + | help: try: `target_os = "hermit"` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:12:1 + | +LL | #[cfg(wasi)] + | ^^^^^^----^^ + | | + | help: try: `target_os = "wasi"` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:15:1 + | +LL | #[cfg(none)] + | ^^^^^^----^^ + | | + | help: try: `target_os = "none"` + +error: operating system used in target family position + --> $DIR/mismatched_target_os_non_unix.rs:19:1 + | +LL | #[cfg(all(not(any(windows, cloudabi)), wasi))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | #[cfg(all(not(any(windows, target_os = "cloudabi")), wasi))] + | ^^^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | #[cfg(all(not(any(windows, cloudabi)), target_os = "wasi"))] + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_unix.fixed b/src/tools/clippy/tests/ui/mismatched_target_os_unix.fixed new file mode 100644 index 0000000000000..7d9d406d99dfc --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_unix.fixed @@ -0,0 +1,62 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(target_os = "linux")] +fn linux() {} + +#[cfg(target_os = "freebsd")] +fn freebsd() {} + +#[cfg(target_os = "dragonfly")] +fn dragonfly() {} + +#[cfg(target_os = "openbsd")] +fn openbsd() {} + +#[cfg(target_os = "netbsd")] +fn netbsd() {} + +#[cfg(target_os = "macos")] +fn macos() {} + +#[cfg(target_os = "ios")] +fn ios() {} + +#[cfg(target_os = "android")] +fn android() {} + +#[cfg(target_os = "emscripten")] +fn emscripten() {} + +#[cfg(target_os = "fuchsia")] +fn fuchsia() {} + +#[cfg(target_os = "haiku")] +fn haiku() {} + +#[cfg(target_os = "illumos")] +fn illumos() {} + +#[cfg(target_os = "l4re")] +fn l4re() {} + +#[cfg(target_os = "redox")] +fn redox() {} + +#[cfg(target_os = "solaris")] +fn solaris() {} + +#[cfg(target_os = "vxworks")] +fn vxworks() {} + +// list with conditions +#[cfg(all(not(any(target_os = "solaris", target_os = "linux")), target_os = "freebsd"))] +fn list() {} + +// correct use, should be ignored +#[cfg(target_os = "freebsd")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_unix.rs b/src/tools/clippy/tests/ui/mismatched_target_os_unix.rs new file mode 100644 index 0000000000000..c1177f1eedc62 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_unix.rs @@ -0,0 +1,62 @@ +// run-rustfix + +#![warn(clippy::mismatched_target_os)] +#![allow(unused)] + +#[cfg(linux)] +fn linux() {} + +#[cfg(freebsd)] +fn freebsd() {} + +#[cfg(dragonfly)] +fn dragonfly() {} + +#[cfg(openbsd)] +fn openbsd() {} + +#[cfg(netbsd)] +fn netbsd() {} + +#[cfg(macos)] +fn macos() {} + +#[cfg(ios)] +fn ios() {} + +#[cfg(android)] +fn android() {} + +#[cfg(emscripten)] +fn emscripten() {} + +#[cfg(fuchsia)] +fn fuchsia() {} + +#[cfg(haiku)] +fn haiku() {} + +#[cfg(illumos)] +fn illumos() {} + +#[cfg(l4re)] +fn l4re() {} + +#[cfg(redox)] +fn redox() {} + +#[cfg(solaris)] +fn solaris() {} + +#[cfg(vxworks)] +fn vxworks() {} + +// list with conditions +#[cfg(all(not(any(solaris, linux)), freebsd))] +fn list() {} + +// correct use, should be ignored +#[cfg(target_os = "freebsd")] +fn correct() {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/mismatched_target_os_unix.stderr b/src/tools/clippy/tests/ui/mismatched_target_os_unix.stderr new file mode 100644 index 0000000000000..fe9aeedb59c45 --- /dev/null +++ b/src/tools/clippy/tests/ui/mismatched_target_os_unix.stderr @@ -0,0 +1,183 @@ +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:6:1 + | +LL | #[cfg(linux)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "linux"` + | + = note: `-D clippy::mismatched-target-os` implied by `-D warnings` + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:9:1 + | +LL | #[cfg(freebsd)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "freebsd"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:12:1 + | +LL | #[cfg(dragonfly)] + | ^^^^^^---------^^ + | | + | help: try: `target_os = "dragonfly"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:15:1 + | +LL | #[cfg(openbsd)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "openbsd"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:18:1 + | +LL | #[cfg(netbsd)] + | ^^^^^^------^^ + | | + | help: try: `target_os = "netbsd"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:21:1 + | +LL | #[cfg(macos)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "macos"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:24:1 + | +LL | #[cfg(ios)] + | ^^^^^^---^^ + | | + | help: try: `target_os = "ios"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:27:1 + | +LL | #[cfg(android)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "android"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:30:1 + | +LL | #[cfg(emscripten)] + | ^^^^^^----------^^ + | | + | help: try: `target_os = "emscripten"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:33:1 + | +LL | #[cfg(fuchsia)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "fuchsia"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:36:1 + | +LL | #[cfg(haiku)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "haiku"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:39:1 + | +LL | #[cfg(illumos)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "illumos"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:42:1 + | +LL | #[cfg(l4re)] + | ^^^^^^----^^ + | | + | help: try: `target_os = "l4re"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:45:1 + | +LL | #[cfg(redox)] + | ^^^^^^-----^^ + | | + | help: try: `target_os = "redox"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:48:1 + | +LL | #[cfg(solaris)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "solaris"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:51:1 + | +LL | #[cfg(vxworks)] + | ^^^^^^-------^^ + | | + | help: try: `target_os = "vxworks"` + | + = help: Did you mean `unix`? + +error: operating system used in target family position + --> $DIR/mismatched_target_os_unix.rs:55:1 + | +LL | #[cfg(all(not(any(solaris, linux)), freebsd))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: Did you mean `unix`? +help: try + | +LL | #[cfg(all(not(any(target_os = "solaris", linux)), freebsd))] + | ^^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | #[cfg(all(not(any(solaris, target_os = "linux")), freebsd))] + | ^^^^^^^^^^^^^^^^^^^ +help: try + | +LL | #[cfg(all(not(any(solaris, linux)), target_os = "freebsd"))] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/missing-doc-crate-missing.rs b/src/tools/clippy/tests/ui/missing-doc-crate-missing.rs new file mode 100644 index 0000000000000..51fd57df8df1d --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-crate-missing.rs @@ -0,0 +1,3 @@ +#![warn(clippy::missing_docs_in_private_items)] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing-doc-crate-missing.stderr b/src/tools/clippy/tests/ui/missing-doc-crate-missing.stderr new file mode 100644 index 0000000000000..da46f9886366c --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-crate-missing.stderr @@ -0,0 +1,12 @@ +error: missing documentation for crate + --> $DIR/missing-doc-crate-missing.rs:1:1 + | +LL | / #![warn(clippy::missing_docs_in_private_items)] +LL | | +LL | | fn main() {} + | |____________^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/missing-doc-crate.rs b/src/tools/clippy/tests/ui/missing-doc-crate.rs new file mode 100644 index 0000000000000..04711f864886b --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-crate.rs @@ -0,0 +1,5 @@ +#![warn(clippy::missing_docs_in_private_items)] +#![feature(external_doc)] +#![doc(include = "../../README.md")] + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing-doc-crate.stderr b/src/tools/clippy/tests/ui/missing-doc-crate.stderr new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/clippy/tests/ui/missing-doc-impl.rs b/src/tools/clippy/tests/ui/missing-doc-impl.rs new file mode 100644 index 0000000000000..57af84dcdf4d0 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-impl.rs @@ -0,0 +1,87 @@ +#![warn(clippy::missing_docs_in_private_items)] +#![allow(dead_code)] +#![feature(associated_type_defaults)] + +//! Some garbage docs for the crate here +#![doc = "More garbage"] + +struct Foo { + a: isize, + b: isize, +} + +pub struct PubFoo { + pub a: isize, + b: isize, +} + +#[allow(clippy::missing_docs_in_private_items)] +pub struct PubFoo2 { + pub a: isize, + pub c: isize, +} + +/// dox +pub trait A { + /// dox + fn foo(&self); + /// dox + fn foo_with_impl(&self) {} +} + +#[allow(clippy::missing_docs_in_private_items)] +trait B { + fn foo(&self); + fn foo_with_impl(&self) {} +} + +pub trait C { + fn foo(&self); + fn foo_with_impl(&self) {} +} + +#[allow(clippy::missing_docs_in_private_items)] +pub trait D { + fn dummy(&self) {} +} + +/// dox +pub trait E: Sized { + type AssociatedType; + type AssociatedTypeDef = Self; + + /// dox + type DocumentedType; + /// dox + type DocumentedTypeDef = Self; + /// dox + fn dummy(&self) {} +} + +impl Foo { + pub fn foo() {} + fn bar() {} +} + +impl PubFoo { + pub fn foo() {} + /// dox + pub fn foo1() {} + fn foo2() {} + #[allow(clippy::missing_docs_in_private_items)] + pub fn foo3() {} +} + +#[allow(clippy::missing_docs_in_private_items)] +trait F { + fn a(); + fn b(&self); +} + +// should need to redefine documentation for implementations of traits +impl F for Foo { + fn a() {} + fn b(&self) {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing-doc-impl.stderr b/src/tools/clippy/tests/ui/missing-doc-impl.stderr new file mode 100644 index 0000000000000..9656a39abceb5 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc-impl.stderr @@ -0,0 +1,103 @@ +error: missing documentation for a struct + --> $DIR/missing-doc-impl.rs:8:1 + | +LL | / struct Foo { +LL | | a: isize, +LL | | b: isize, +LL | | } + | |_^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:9:5 + | +LL | a: isize, + | ^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:10:5 + | +LL | b: isize, + | ^^^^^^^^ + +error: missing documentation for a struct + --> $DIR/missing-doc-impl.rs:13:1 + | +LL | / pub struct PubFoo { +LL | | pub a: isize, +LL | | b: isize, +LL | | } + | |_^ + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:14:5 + | +LL | pub a: isize, + | ^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc-impl.rs:15:5 + | +LL | b: isize, + | ^^^^^^^^ + +error: missing documentation for a trait + --> $DIR/missing-doc-impl.rs:38:1 + | +LL | / pub trait C { +LL | | fn foo(&self); +LL | | fn foo_with_impl(&self) {} +LL | | } + | |_^ + +error: missing documentation for a trait method + --> $DIR/missing-doc-impl.rs:39:5 + | +LL | fn foo(&self); + | ^^^^^^^^^^^^^^ + +error: missing documentation for a trait method + --> $DIR/missing-doc-impl.rs:40:5 + | +LL | fn foo_with_impl(&self) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for an associated type + --> $DIR/missing-doc-impl.rs:50:5 + | +LL | type AssociatedType; + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for an associated type + --> $DIR/missing-doc-impl.rs:51:5 + | +LL | type AssociatedTypeDef = Self; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a method + --> $DIR/missing-doc-impl.rs:62:5 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + +error: missing documentation for a method + --> $DIR/missing-doc-impl.rs:63:5 + | +LL | fn bar() {} + | ^^^^^^^^^^^ + +error: missing documentation for a method + --> $DIR/missing-doc-impl.rs:67:5 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + +error: missing documentation for a method + --> $DIR/missing-doc-impl.rs:70:5 + | +LL | fn foo2() {} + | ^^^^^^^^^^^^ + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/missing-doc.rs b/src/tools/clippy/tests/ui/missing-doc.rs new file mode 100644 index 0000000000000..a9bf7140a1e59 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc.rs @@ -0,0 +1,102 @@ +#![warn(clippy::missing_docs_in_private_items)] +// When denying at the crate level, be sure to not get random warnings from the +// injected intrinsics by the compiler. +#![allow(dead_code)] +#![feature(global_asm)] + +//! Some garbage docs for the crate here +#![doc = "More garbage"] + +type Typedef = String; +pub type PubTypedef = String; + +mod module_no_dox {} +pub mod pub_module_no_dox {} + +/// dox +pub fn foo() {} +pub fn foo2() {} +fn foo3() {} +#[allow(clippy::missing_docs_in_private_items)] +pub fn foo4() {} + +// It sure is nice if doc(hidden) implies allow(missing_docs), and that it +// applies recursively +#[doc(hidden)] +mod a { + pub fn baz() {} + pub mod b { + pub fn baz() {} + } +} + +enum Baz { + BazA { a: isize, b: isize }, + BarB, +} + +pub enum PubBaz { + PubBazA { a: isize }, +} + +/// dox +pub enum PubBaz2 { + /// dox + PubBaz2A { + /// dox + a: isize, + }, +} + +#[allow(clippy::missing_docs_in_private_items)] +pub enum PubBaz3 { + PubBaz3A { b: isize }, +} + +#[doc(hidden)] +pub fn baz() {} + +const FOO: u32 = 0; +/// dox +pub const FOO1: u32 = 0; +#[allow(clippy::missing_docs_in_private_items)] +pub const FOO2: u32 = 0; +#[doc(hidden)] +pub const FOO3: u32 = 0; +pub const FOO4: u32 = 0; + +static BAR: u32 = 0; +/// dox +pub static BAR1: u32 = 0; +#[allow(clippy::missing_docs_in_private_items)] +pub static BAR2: u32 = 0; +#[doc(hidden)] +pub static BAR3: u32 = 0; +pub static BAR4: u32 = 0; + +mod internal_impl { + /// dox + pub fn documented() {} + pub fn undocumented1() {} + pub fn undocumented2() {} + fn undocumented3() {} + /// dox + pub mod globbed { + /// dox + pub fn also_documented() {} + pub fn also_undocumented1() {} + fn also_undocumented2() {} + } +} +/// dox +pub mod public_interface { + pub use internal_impl::documented as foo; + pub use internal_impl::globbed::*; + pub use internal_impl::undocumented1 as bar; + pub use internal_impl::{documented, undocumented2}; +} + +fn main() {} + +// Ensure global asm doesn't require documentation. +global_asm! { "" } diff --git a/src/tools/clippy/tests/ui/missing-doc.stderr b/src/tools/clippy/tests/ui/missing-doc.stderr new file mode 100644 index 0000000000000..a876dc078ebff --- /dev/null +++ b/src/tools/clippy/tests/ui/missing-doc.stderr @@ -0,0 +1,159 @@ +error: missing documentation for a type alias + --> $DIR/missing-doc.rs:10:1 + | +LL | type Typedef = String; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: missing documentation for a type alias + --> $DIR/missing-doc.rs:11:1 + | +LL | pub type PubTypedef = String; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a module + --> $DIR/missing-doc.rs:13:1 + | +LL | mod module_no_dox {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a module + --> $DIR/missing-doc.rs:14:1 + | +LL | pub mod pub_module_no_dox {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:18:1 + | +LL | pub fn foo2() {} + | ^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:19:1 + | +LL | fn foo3() {} + | ^^^^^^^^^^^^ + +error: missing documentation for an enum + --> $DIR/missing-doc.rs:33:1 + | +LL | / enum Baz { +LL | | BazA { a: isize, b: isize }, +LL | | BarB, +LL | | } + | |_^ + +error: missing documentation for a variant + --> $DIR/missing-doc.rs:34:5 + | +LL | BazA { a: isize, b: isize }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc.rs:34:12 + | +LL | BazA { a: isize, b: isize }, + | ^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc.rs:34:22 + | +LL | BazA { a: isize, b: isize }, + | ^^^^^^^^ + +error: missing documentation for a variant + --> $DIR/missing-doc.rs:35:5 + | +LL | BarB, + | ^^^^ + +error: missing documentation for an enum + --> $DIR/missing-doc.rs:38:1 + | +LL | / pub enum PubBaz { +LL | | PubBazA { a: isize }, +LL | | } + | |_^ + +error: missing documentation for a variant + --> $DIR/missing-doc.rs:39:5 + | +LL | PubBazA { a: isize }, + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/missing-doc.rs:39:15 + | +LL | PubBazA { a: isize }, + | ^^^^^^^^ + +error: missing documentation for a constant + --> $DIR/missing-doc.rs:59:1 + | +LL | const FOO: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a constant + --> $DIR/missing-doc.rs:66:1 + | +LL | pub const FOO4: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a static + --> $DIR/missing-doc.rs:68:1 + | +LL | static BAR: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a static + --> $DIR/missing-doc.rs:75:1 + | +LL | pub static BAR4: u32 = 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a module + --> $DIR/missing-doc.rs:77:1 + | +LL | / mod internal_impl { +LL | | /// dox +LL | | pub fn documented() {} +LL | | pub fn undocumented1() {} +... | +LL | | } +LL | | } + | |_^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:80:5 + | +LL | pub fn undocumented1() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:81:5 + | +LL | pub fn undocumented2() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:82:5 + | +LL | fn undocumented3() {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:87:9 + | +LL | pub fn also_undocumented1() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/missing-doc.rs:88:9 + | +LL | fn also_undocumented2() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 24 previous errors + diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.rs b/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.rs new file mode 100644 index 0000000000000..ba352ef9ee932 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -0,0 +1,103 @@ +//! False-positive tests to ensure we don't suggest `const` for things where it would cause a +//! compilation error. +//! The .stderr output of this test should be empty. Otherwise it's a bug somewhere. + +#![warn(clippy::missing_const_for_fn)] +#![allow(incomplete_features)] +#![feature(start, const_generics)] + +struct Game; + +// This should not be linted because it's already const +const fn already_const() -> i32 { + 32 +} + +impl Game { + // This should not be linted because it's already const + pub const fn already_const() -> i32 { + 32 + } +} + +// Allowing on this function, because it would lint, which we don't want in this case. +#[allow(clippy::missing_const_for_fn)] +fn random() -> u32 { + 42 +} + +// We should not suggest to make this function `const` because `random()` is non-const +fn random_caller() -> u32 { + random() +} + +static Y: u32 = 0; + +// We should not suggest to make this function `const` because const functions are not allowed to +// refer to a static variable +fn get_y() -> u32 { + Y + //~^ ERROR E0013 +} + +// Don't lint entrypoint functions +#[start] +fn init(num: isize, something: *const *const u8) -> isize { + 1 +} + +trait Foo { + // This should not be suggested to be made const + // (rustc doesn't allow const trait methods) + fn f() -> u32; + + // This should not be suggested to be made const either + fn g() -> u32 { + 33 + } +} + +// Don't lint in external macros (derive) +#[derive(PartialEq, Eq)] +struct Point(isize, isize); + +impl std::ops::Add for Point { + type Output = Self; + + // Don't lint in trait impls of derived methods + fn add(self, other: Self) -> Self { + Point(self.0 + other.0, self.1 + other.1) + } +} + +mod with_drop { + pub struct A; + pub struct B; + impl Drop for A { + fn drop(&mut self) {} + } + + impl A { + // This can not be const because the type implements `Drop`. + pub fn a(self) -> B { + B + } + } + + impl B { + // This can not be const because `a` implements `Drop`. + pub fn a(self, a: A) -> B { + B + } + } +} + +fn const_generic_params(t: &[T; N]) -> &[T; N] { + t +} + +fn const_generic_return(t: &[T]) -> &[T; N] { + let p = t.as_ptr() as *const [T; N]; + + unsafe { &*p } +} diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.stderr b/src/tools/clippy/tests/ui/missing_const_for_fn/cant_be_const.stderr new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.rs b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.rs new file mode 100644 index 0000000000000..c6f44b7daa342 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.rs @@ -0,0 +1,74 @@ +#![warn(clippy::missing_const_for_fn)] +#![allow(incomplete_features, clippy::let_and_return)] +#![feature(const_generics)] + +use std::mem::transmute; + +struct Game { + guess: i32, +} + +impl Game { + // Could be const + pub fn new() -> Self { + Self { guess: 42 } + } + + fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { + b + } +} + +// Could be const +fn one() -> i32 { + 1 +} + +// Could also be const +fn two() -> i32 { + let abc = 2; + abc +} + +// Could be const (since Rust 1.39) +fn string() -> String { + String::new() +} + +// Could be const +unsafe fn four() -> i32 { + 4 +} + +// Could also be const +fn generic(t: T) -> T { + t +} + +fn sub(x: u32) -> usize { + unsafe { transmute(&x) } +} + +// NOTE: This is currently not yet allowed to be const +// Once implemented, Clippy should be able to suggest this as const, too. +fn generic_arr(t: [T; 1]) -> T { + t[0] +} + +mod with_drop { + pub struct A; + pub struct B; + impl Drop for A { + fn drop(&mut self) {} + } + + impl B { + // This can be const, because `a` is passed by reference + pub fn b(self, a: &A) -> B { + B + } + } +} + +// Should not be const +fn main() {} diff --git a/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.stderr b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.stderr new file mode 100644 index 0000000000000..8dde56cd79f44 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -0,0 +1,77 @@ +error: this could be a `const fn` + --> $DIR/could_be_const.rs:13:5 + | +LL | / pub fn new() -> Self { +LL | | Self { guess: 42 } +LL | | } + | |_____^ + | + = note: `-D clippy::missing-const-for-fn` implied by `-D warnings` + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:17:5 + | +LL | / fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { +LL | | b +LL | | } + | |_____^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:23:1 + | +LL | / fn one() -> i32 { +LL | | 1 +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:28:1 + | +LL | / fn two() -> i32 { +LL | | let abc = 2; +LL | | abc +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:34:1 + | +LL | / fn string() -> String { +LL | | String::new() +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:39:1 + | +LL | / unsafe fn four() -> i32 { +LL | | 4 +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:44:1 + | +LL | / fn generic(t: T) -> T { +LL | | t +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:48:1 + | +LL | / fn sub(x: u32) -> usize { +LL | | unsafe { transmute(&x) } +LL | | } + | |_^ + +error: this could be a `const fn` + --> $DIR/could_be_const.rs:67:9 + | +LL | / pub fn b(self, a: &A) -> B { +LL | | B +LL | | } + | |_________^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/missing_inline.rs b/src/tools/clippy/tests/ui/missing_inline.rs new file mode 100644 index 0000000000000..b73b24b8e0a3b --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline.rs @@ -0,0 +1,66 @@ +#![warn(clippy::missing_inline_in_public_items)] +#![crate_type = "dylib"] +// When denying at the crate level, be sure to not get random warnings from the +// injected intrinsics by the compiler. +#![allow(dead_code, non_snake_case)] + +type Typedef = String; +pub type PubTypedef = String; + +struct Foo {} // ok +pub struct PubFoo {} // ok +enum FooE {} // ok +pub enum PubFooE {} // ok + +mod module {} // ok +pub mod pub_module {} // ok + +fn foo() {} +pub fn pub_foo() {} // missing #[inline] +#[inline] +pub fn pub_foo_inline() {} // ok +#[inline(always)] +pub fn pub_foo_inline_always() {} // ok + +#[allow(clippy::missing_inline_in_public_items)] +pub fn pub_foo_no_inline() {} + +trait Bar { + fn Bar_a(); // ok + fn Bar_b() {} // ok +} + +pub trait PubBar { + fn PubBar_a(); // ok + fn PubBar_b() {} // missing #[inline] + #[inline] + fn PubBar_c() {} // ok +} + +// none of these need inline because Foo is not exported +impl PubBar for Foo { + fn PubBar_a() {} // ok + fn PubBar_b() {} // ok + fn PubBar_c() {} // ok +} + +// all of these need inline because PubFoo is exported +impl PubBar for PubFoo { + fn PubBar_a() {} // missing #[inline] + fn PubBar_b() {} // missing #[inline] + fn PubBar_c() {} // missing #[inline] +} + +// do not need inline because Foo is not exported +impl Foo { + fn FooImpl() {} // ok +} + +// need inline because PubFoo is exported +impl PubFoo { + pub fn PubFooImpl() {} // missing #[inline] +} + +// do not lint this since users cannot control the external code +#[derive(Debug)] +pub struct S {} diff --git a/src/tools/clippy/tests/ui/missing_inline.stderr b/src/tools/clippy/tests/ui/missing_inline.stderr new file mode 100644 index 0000000000000..40b92b7647bf7 --- /dev/null +++ b/src/tools/clippy/tests/ui/missing_inline.stderr @@ -0,0 +1,40 @@ +error: missing `#[inline]` for a function + --> $DIR/missing_inline.rs:19:1 + | +LL | pub fn pub_foo() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-inline-in-public-items` implied by `-D warnings` + +error: missing `#[inline]` for a default trait method + --> $DIR/missing_inline.rs:35:5 + | +LL | fn PubBar_b() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:49:5 + | +LL | fn PubBar_a() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:50:5 + | +LL | fn PubBar_b() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:51:5 + | +LL | fn PubBar_c() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^ + +error: missing `#[inline]` for a method + --> $DIR/missing_inline.rs:61:5 + | +LL | pub fn PubFooImpl() {} // missing #[inline] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/mistyped_literal_suffix.fixed b/src/tools/clippy/tests/ui/mistyped_literal_suffix.fixed new file mode 100644 index 0000000000000..baee773573038 --- /dev/null +++ b/src/tools/clippy/tests/ui/mistyped_literal_suffix.fixed @@ -0,0 +1,24 @@ +// run-rustfix + +#![allow(dead_code, unused_variables, clippy::excessive_precision)] + +fn main() { + let fail14 = 2_i32; + let fail15 = 4_i64; + let fail16 = 7_i8; // + let fail17 = 23_i16; // + let ok18 = 23_128; + + let fail20 = 2_i8; // + let fail21 = 4_i16; // + + let fail24 = 12.34_f64; + let fail25 = 1E2_f32; + let fail26 = 43E7_f64; + let fail27 = 243E17_f32; + #[allow(overflowing_literals)] + let fail28 = 241_251_235E723_f64; + let fail29 = 42_279.911_f32; + + let _ = 1.123_45E1_f32; +} diff --git a/src/tools/clippy/tests/ui/mistyped_literal_suffix.rs b/src/tools/clippy/tests/ui/mistyped_literal_suffix.rs new file mode 100644 index 0000000000000..6de447f40214b --- /dev/null +++ b/src/tools/clippy/tests/ui/mistyped_literal_suffix.rs @@ -0,0 +1,24 @@ +// run-rustfix + +#![allow(dead_code, unused_variables, clippy::excessive_precision)] + +fn main() { + let fail14 = 2_32; + let fail15 = 4_64; + let fail16 = 7_8; // + let fail17 = 23_16; // + let ok18 = 23_128; + + let fail20 = 2__8; // + let fail21 = 4___16; // + + let fail24 = 12.34_64; + let fail25 = 1E2_32; + let fail26 = 43E7_64; + let fail27 = 243E17_32; + #[allow(overflowing_literals)] + let fail28 = 241251235E723_64; + let fail29 = 42279.911_32; + + let _ = 1.12345E1_32; +} diff --git a/src/tools/clippy/tests/ui/mistyped_literal_suffix.stderr b/src/tools/clippy/tests/ui/mistyped_literal_suffix.stderr new file mode 100644 index 0000000000000..48a7ae904948c --- /dev/null +++ b/src/tools/clippy/tests/ui/mistyped_literal_suffix.stderr @@ -0,0 +1,82 @@ +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:6:18 + | +LL | let fail14 = 2_32; + | ^^^^ help: did you mean to write: `2_i32` + | + = note: `#[deny(clippy::mistyped_literal_suffixes)]` on by default + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:7:18 + | +LL | let fail15 = 4_64; + | ^^^^ help: did you mean to write: `4_i64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:8:18 + | +LL | let fail16 = 7_8; // + | ^^^ help: did you mean to write: `7_i8` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:9:18 + | +LL | let fail17 = 23_16; // + | ^^^^^ help: did you mean to write: `23_i16` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:12:18 + | +LL | let fail20 = 2__8; // + | ^^^^ help: did you mean to write: `2_i8` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:13:18 + | +LL | let fail21 = 4___16; // + | ^^^^^^ help: did you mean to write: `4_i16` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:15:18 + | +LL | let fail24 = 12.34_64; + | ^^^^^^^^ help: did you mean to write: `12.34_f64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:16:18 + | +LL | let fail25 = 1E2_32; + | ^^^^^^ help: did you mean to write: `1E2_f32` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:17:18 + | +LL | let fail26 = 43E7_64; + | ^^^^^^^ help: did you mean to write: `43E7_f64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:18:18 + | +LL | let fail27 = 243E17_32; + | ^^^^^^^^^ help: did you mean to write: `243E17_f32` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:20:18 + | +LL | let fail28 = 241251235E723_64; + | ^^^^^^^^^^^^^^^^ help: did you mean to write: `241_251_235E723_f64` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:21:18 + | +LL | let fail29 = 42279.911_32; + | ^^^^^^^^^^^^ help: did you mean to write: `42_279.911_f32` + +error: mistyped literal suffix + --> $DIR/mistyped_literal_suffix.rs:23:13 + | +LL | let _ = 1.12345E1_32; + | ^^^^^^^^^^^^ help: did you mean to write: `1.123_45E1_f32` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/module_inception.rs b/src/tools/clippy/tests/ui/module_inception.rs new file mode 100644 index 0000000000000..a23aba9164a5c --- /dev/null +++ b/src/tools/clippy/tests/ui/module_inception.rs @@ -0,0 +1,21 @@ +#![warn(clippy::module_inception)] + +mod foo { + mod bar { + mod bar { + mod foo {} + } + mod foo {} + } + mod foo { + mod bar {} + } +} + +// No warning. See . +mod bar { + #[allow(clippy::module_inception)] + mod bar {} +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/module_inception.stderr b/src/tools/clippy/tests/ui/module_inception.stderr new file mode 100644 index 0000000000000..77564dce9eb48 --- /dev/null +++ b/src/tools/clippy/tests/ui/module_inception.stderr @@ -0,0 +1,20 @@ +error: module has the same name as its containing module + --> $DIR/module_inception.rs:5:9 + | +LL | / mod bar { +LL | | mod foo {} +LL | | } + | |_________^ + | + = note: `-D clippy::module-inception` implied by `-D warnings` + +error: module has the same name as its containing module + --> $DIR/module_inception.rs:10:5 + | +LL | / mod foo { +LL | | mod bar {} +LL | | } + | |_____^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/module_name_repetitions.rs b/src/tools/clippy/tests/ui/module_name_repetitions.rs new file mode 100644 index 0000000000000..669bf01a84c10 --- /dev/null +++ b/src/tools/clippy/tests/ui/module_name_repetitions.rs @@ -0,0 +1,26 @@ +// compile-flags: --test + +#![warn(clippy::module_name_repetitions)] +#![allow(dead_code)] + +mod foo { + pub fn foo() {} + pub fn foo_bar() {} + pub fn bar_foo() {} + pub struct FooCake {} + pub enum CakeFoo {} + pub struct Foo7Bar; + + // Should not warn + pub struct Foobar; +} + +#[cfg(test)] +mod test { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/module_name_repetitions.stderr b/src/tools/clippy/tests/ui/module_name_repetitions.stderr new file mode 100644 index 0000000000000..bdd217a969c05 --- /dev/null +++ b/src/tools/clippy/tests/ui/module_name_repetitions.stderr @@ -0,0 +1,34 @@ +error: item name starts with its containing module's name + --> $DIR/module_name_repetitions.rs:8:5 + | +LL | pub fn foo_bar() {} + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::module-name-repetitions` implied by `-D warnings` + +error: item name ends with its containing module's name + --> $DIR/module_name_repetitions.rs:9:5 + | +LL | pub fn bar_foo() {} + | ^^^^^^^^^^^^^^^^^^^ + +error: item name starts with its containing module's name + --> $DIR/module_name_repetitions.rs:10:5 + | +LL | pub struct FooCake {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: item name ends with its containing module's name + --> $DIR/module_name_repetitions.rs:11:5 + | +LL | pub enum CakeFoo {} + | ^^^^^^^^^^^^^^^^^^^ + +error: item name starts with its containing module's name + --> $DIR/module_name_repetitions.rs:12:5 + | +LL | pub struct Foo7Bar; + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_float.rs b/src/tools/clippy/tests/ui/modulo_arithmetic_float.rs new file mode 100644 index 0000000000000..b010b0dbdfa69 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_float.rs @@ -0,0 +1,36 @@ +#![warn(clippy::modulo_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::modulo_one +)] + +fn main() { + // Lint when both sides are const and of the opposite sign + -1.6 % 2.1; + 1.6 % -2.1; + (1.1 - 2.3) % (1.1 + 2.3); + (1.1 + 2.3) % (1.1 - 2.3); + + // Lint on floating point numbers + let a_f32: f32 = -1.6; + let mut b_f32: f32 = 2.1; + a_f32 % b_f32; + b_f32 % a_f32; + b_f32 %= a_f32; + + let a_f64: f64 = -1.6; + let mut b_f64: f64 = 2.1; + a_f64 % b_f64; + b_f64 % a_f64; + b_f64 %= a_f64; + + // No lint when both sides are const and of the same sign + 1.6 % 2.1; + -1.6 % -2.1; + (1.1 + 2.3) % (-1.1 + 2.3); + (-1.1 - 2.3) % (1.1 - 2.3); +} diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_float.stderr b/src/tools/clippy/tests/ui/modulo_arithmetic_float.stderr new file mode 100644 index 0000000000000..7bfdb0bde6070 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_float.stderr @@ -0,0 +1,83 @@ +error: you are using modulo operator on constants with different signs: `-1.600 % 2.100` + --> $DIR/modulo_arithmetic_float.rs:13:5 + | +LL | -1.6 % 2.1; + | ^^^^^^^^^^ + | + = note: `-D clippy::modulo-arithmetic` implied by `-D warnings` + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on constants with different signs: `1.600 % -2.100` + --> $DIR/modulo_arithmetic_float.rs:14:5 + | +LL | 1.6 % -2.1; + | ^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on constants with different signs: `-1.200 % 3.400` + --> $DIR/modulo_arithmetic_float.rs:15:5 + | +LL | (1.1 - 2.3) % (1.1 + 2.3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on constants with different signs: `3.400 % -1.200` + --> $DIR/modulo_arithmetic_float.rs:16:5 + | +LL | (1.1 + 2.3) % (1.1 - 2.3); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:21:5 + | +LL | a_f32 % b_f32; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:22:5 + | +LL | b_f32 % a_f32; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:23:5 + | +LL | b_f32 %= a_f32; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:27:5 + | +LL | a_f64 % b_f64; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:28:5 + | +LL | b_f64 % a_f64; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_float.rs:29:5 + | +LL | b_f64 %= a_f64; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral.rs b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.rs new file mode 100644 index 0000000000000..779d035c5f8a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.rs @@ -0,0 +1,90 @@ +#![warn(clippy::modulo_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::modulo_one +)] + +fn main() { + // Lint on signed integral numbers + let a = -1; + let mut b = 2; + a % b; + b % a; + b %= a; + + let a_i8: i8 = 1; + let mut b_i8: i8 = 2; + a_i8 % b_i8; + b_i8 %= a_i8; + + let a_i16: i16 = 1; + let mut b_i16: i16 = 2; + a_i16 % b_i16; + b_i16 %= a_i16; + + let a_i32: i32 = 1; + let mut b_i32: i32 = 2; + a_i32 % b_i32; + b_i32 %= a_i32; + + let a_i64: i64 = 1; + let mut b_i64: i64 = 2; + a_i64 % b_i64; + b_i64 %= a_i64; + + let a_i128: i128 = 1; + let mut b_i128: i128 = 2; + a_i128 % b_i128; + b_i128 %= a_i128; + + let a_isize: isize = 1; + let mut b_isize: isize = 2; + a_isize % b_isize; + b_isize %= a_isize; + + let a = 1; + let mut b = 2; + a % b; + b %= a; + + // No lint on unsigned integral value + let a_u8: u8 = 17; + let b_u8: u8 = 3; + a_u8 % b_u8; + let mut a_u8: u8 = 1; + a_u8 %= 2; + + let a_u16: u16 = 17; + let b_u16: u16 = 3; + a_u16 % b_u16; + let mut a_u16: u16 = 1; + a_u16 %= 2; + + let a_u32: u32 = 17; + let b_u32: u32 = 3; + a_u32 % b_u32; + let mut a_u32: u32 = 1; + a_u32 %= 2; + + let a_u64: u64 = 17; + let b_u64: u64 = 3; + a_u64 % b_u64; + let mut a_u64: u64 = 1; + a_u64 %= 2; + + let a_u128: u128 = 17; + let b_u128: u128 = 3; + a_u128 % b_u128; + let mut a_u128: u128 = 1; + a_u128 %= 2; + + let a_usize: usize = 17; + let b_usize: usize = 3; + a_usize % b_usize; + let mut a_usize: usize = 1; + a_usize %= 2; +} diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral.stderr b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.stderr new file mode 100644 index 0000000000000..e863b838699e9 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral.stderr @@ -0,0 +1,156 @@ +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:15:5 + | +LL | a % b; + | ^^^^^ + | + = note: `-D clippy::modulo-arithmetic` implied by `-D warnings` + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:16:5 + | +LL | b % a; + | ^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:17:5 + | +LL | b %= a; + | ^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:21:5 + | +LL | a_i8 % b_i8; + | ^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:22:5 + | +LL | b_i8 %= a_i8; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:26:5 + | +LL | a_i16 % b_i16; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:27:5 + | +LL | b_i16 %= a_i16; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:31:5 + | +LL | a_i32 % b_i32; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:32:5 + | +LL | b_i32 %= a_i32; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:36:5 + | +LL | a_i64 % b_i64; + | ^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:37:5 + | +LL | b_i64 %= a_i64; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:41:5 + | +LL | a_i128 % b_i128; + | ^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:42:5 + | +LL | b_i128 %= a_i128; + | ^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:46:5 + | +LL | a_isize % b_isize; + | ^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:47:5 + | +LL | b_isize %= a_isize; + | ^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:51:5 + | +LL | a % b; + | ^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on types that might have different signs + --> $DIR/modulo_arithmetic_integral.rs:52:5 + | +LL | b %= a; + | ^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.rs b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.rs new file mode 100644 index 0000000000000..57a96692c0097 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.rs @@ -0,0 +1,44 @@ +#![warn(clippy::modulo_arithmetic)] +#![allow( + unused, + clippy::shadow_reuse, + clippy::shadow_unrelated, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::modulo_one +)] + +fn main() { + // Lint when both sides are const and of the opposite sign + -1 % 2; + 1 % -2; + (1 - 2) % (1 + 2); + (1 + 2) % (1 - 2); + 35 * (7 - 4 * 2) % (-500 * -600); + + -1i8 % 2i8; + 1i8 % -2i8; + -1i16 % 2i16; + 1i16 % -2i16; + -1i32 % 2i32; + 1i32 % -2i32; + -1i64 % 2i64; + 1i64 % -2i64; + -1i128 % 2i128; + 1i128 % -2i128; + -1isize % 2isize; + 1isize % -2isize; + + // No lint when both sides are const and of the same sign + 1 % 2; + -1 % -2; + (1 + 2) % (-1 + 2); + (-1 - 2) % (1 - 2); + + 1u8 % 2u8; + 1u16 % 2u16; + 1u32 % 2u32; + 1u64 % 2u64; + 1u128 % 2u128; + 1usize % 2usize; +} diff --git a/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.stderr b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.stderr new file mode 100644 index 0000000000000..de328bb75fe91 --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_arithmetic_integral_const.stderr @@ -0,0 +1,156 @@ +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:13:5 + | +LL | -1 % 2; + | ^^^^^^ + | + = note: `-D clippy::modulo-arithmetic` implied by `-D warnings` + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:14:5 + | +LL | 1 % -2; + | ^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 3` + --> $DIR/modulo_arithmetic_integral_const.rs:15:5 + | +LL | (1 - 2) % (1 + 2); + | ^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `3 % -1` + --> $DIR/modulo_arithmetic_integral_const.rs:16:5 + | +LL | (1 + 2) % (1 - 2); + | ^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-35 % 300000` + --> $DIR/modulo_arithmetic_integral_const.rs:17:5 + | +LL | 35 * (7 - 4 * 2) % (-500 * -600); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:19:5 + | +LL | -1i8 % 2i8; + | ^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:20:5 + | +LL | 1i8 % -2i8; + | ^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:21:5 + | +LL | -1i16 % 2i16; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:22:5 + | +LL | 1i16 % -2i16; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:23:5 + | +LL | -1i32 % 2i32; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:24:5 + | +LL | 1i32 % -2i32; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:25:5 + | +LL | -1i64 % 2i64; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:26:5 + | +LL | 1i64 % -2i64; + | ^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:27:5 + | +LL | -1i128 % 2i128; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:28:5 + | +LL | 1i128 % -2i128; + | ^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `-1 % 2` + --> $DIR/modulo_arithmetic_integral_const.rs:29:5 + | +LL | -1isize % 2isize; + | ^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: you are using modulo operator on constants with different signs: `1 % -2` + --> $DIR/modulo_arithmetic_integral_const.rs:30:5 + | +LL | 1isize % -2isize; + | ^^^^^^^^^^^^^^^^ + | + = note: double check for expected result especially when interoperating with different languages + = note: or consider using `rem_euclid` or similar function + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/modulo_one.rs b/src/tools/clippy/tests/ui/modulo_one.rs new file mode 100644 index 0000000000000..cc8c8e7cdaefd --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_one.rs @@ -0,0 +1,14 @@ +#![warn(clippy::modulo_one)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +static STATIC_ONE: usize = 2 - 1; + +fn main() { + 10 % 1; + 10 % 2; + + const ONE: u32 = 1 * 1; + + 2 % ONE; + 5 % STATIC_ONE; +} diff --git a/src/tools/clippy/tests/ui/modulo_one.stderr b/src/tools/clippy/tests/ui/modulo_one.stderr new file mode 100644 index 0000000000000..6bee68360b6fb --- /dev/null +++ b/src/tools/clippy/tests/ui/modulo_one.stderr @@ -0,0 +1,30 @@ +error: any number modulo 1 will be 0 + --> $DIR/modulo_one.rs:7:5 + | +LL | 10 % 1; + | ^^^^^^ + | + = note: `-D clippy::modulo-one` implied by `-D warnings` + +error: the operation is ineffective. Consider reducing it to `1` + --> $DIR/modulo_one.rs:10:22 + | +LL | const ONE: u32 = 1 * 1; + | ^^^^^ + | + = note: `-D clippy::identity-op` implied by `-D warnings` + +error: the operation is ineffective. Consider reducing it to `1` + --> $DIR/modulo_one.rs:10:22 + | +LL | const ONE: u32 = 1 * 1; + | ^^^^^ + +error: any number modulo 1 will be 0 + --> $DIR/modulo_one.rs:12:5 + | +LL | 2 % ONE; + | ^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/must_use_candidates.fixed b/src/tools/clippy/tests/ui/must_use_candidates.fixed new file mode 100644 index 0000000000000..9556f6f82cc63 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_candidates.fixed @@ -0,0 +1,93 @@ +// run-rustfix +#![feature(never_type)] +#![allow(unused_mut, clippy::redundant_allocation)] +#![warn(clippy::must_use_candidate)] +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +pub struct MyAtomic(AtomicBool); +pub struct MyPure; + +#[must_use] pub fn pure(i: u8) -> u8 { + i +} + +impl MyPure { + #[must_use] pub fn inherent_pure(&self) -> u8 { + 0 + } +} + +pub trait MyPureTrait { + fn trait_pure(&self, i: u32) -> u32 { + self.trait_impl_pure(i) + 1 + } + + fn trait_impl_pure(&self, i: u32) -> u32; +} + +impl MyPureTrait for MyPure { + fn trait_impl_pure(&self, i: u32) -> u32 { + i + } +} + +pub fn without_result() { + // OK +} + +pub fn impure_primitive(i: &mut u8) -> u8 { + *i +} + +pub fn with_callback bool>(f: &F) -> bool { + f(0) +} + +#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + true +} + +pub fn quoth_the_raven(_more: !) -> u32 { + unimplemented!(); +} + +pub fn atomics(b: &AtomicBool) -> bool { + b.load(Ordering::SeqCst) +} + +#[must_use] pub fn rcd(_x: Rc) -> bool { + true +} + +pub fn rcmut(_x: Rc<&mut u32>) -> bool { + true +} + +#[must_use] pub fn arcd(_x: Arc) -> bool { + false +} + +pub fn inner_types(_m: &MyAtomic) -> bool { + true +} + +static mut COUNTER: usize = 0; + +/// # Safety +/// +/// Don't ever call this from multiple threads +pub unsafe fn mutates_static() -> usize { + COUNTER += 1; + COUNTER +} + +#[no_mangle] +pub fn unmangled(i: bool) -> bool { + !i +} + +fn main() { + assert_eq!(1, pure(1)); +} diff --git a/src/tools/clippy/tests/ui/must_use_candidates.rs b/src/tools/clippy/tests/ui/must_use_candidates.rs new file mode 100644 index 0000000000000..3732422017104 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_candidates.rs @@ -0,0 +1,93 @@ +// run-rustfix +#![feature(never_type)] +#![allow(unused_mut, clippy::redundant_allocation)] +#![warn(clippy::must_use_candidate)] +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +pub struct MyAtomic(AtomicBool); +pub struct MyPure; + +pub fn pure(i: u8) -> u8 { + i +} + +impl MyPure { + pub fn inherent_pure(&self) -> u8 { + 0 + } +} + +pub trait MyPureTrait { + fn trait_pure(&self, i: u32) -> u32 { + self.trait_impl_pure(i) + 1 + } + + fn trait_impl_pure(&self, i: u32) -> u32; +} + +impl MyPureTrait for MyPure { + fn trait_impl_pure(&self, i: u32) -> u32 { + i + } +} + +pub fn without_result() { + // OK +} + +pub fn impure_primitive(i: &mut u8) -> u8 { + *i +} + +pub fn with_callback bool>(f: &F) -> bool { + f(0) +} + +pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + true +} + +pub fn quoth_the_raven(_more: !) -> u32 { + unimplemented!(); +} + +pub fn atomics(b: &AtomicBool) -> bool { + b.load(Ordering::SeqCst) +} + +pub fn rcd(_x: Rc) -> bool { + true +} + +pub fn rcmut(_x: Rc<&mut u32>) -> bool { + true +} + +pub fn arcd(_x: Arc) -> bool { + false +} + +pub fn inner_types(_m: &MyAtomic) -> bool { + true +} + +static mut COUNTER: usize = 0; + +/// # Safety +/// +/// Don't ever call this from multiple threads +pub unsafe fn mutates_static() -> usize { + COUNTER += 1; + COUNTER +} + +#[no_mangle] +pub fn unmangled(i: bool) -> bool { + !i +} + +fn main() { + assert_eq!(1, pure(1)); +} diff --git a/src/tools/clippy/tests/ui/must_use_candidates.stderr b/src/tools/clippy/tests/ui/must_use_candidates.stderr new file mode 100644 index 0000000000000..0fa3849d03bff --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_candidates.stderr @@ -0,0 +1,34 @@ +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:12:1 + | +LL | pub fn pure(i: u8) -> u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn pure(i: u8) -> u8` + | + = note: `-D clippy::must-use-candidate` implied by `-D warnings` + +error: this method could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:17:5 + | +LL | pub fn inherent_pure(&self) -> u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn inherent_pure(&self) -> u8` + +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:48:1 + | +LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool` + +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:60:1 + | +LL | pub fn rcd(_x: Rc) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn rcd(_x: Rc) -> bool` + +error: this function could have a `#[must_use]` attribute + --> $DIR/must_use_candidates.rs:68:1 + | +LL | pub fn arcd(_x: Arc) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn arcd(_x: Arc) -> bool` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/must_use_unit.fixed b/src/tools/clippy/tests/ui/must_use_unit.fixed new file mode 100644 index 0000000000000..6c9aa434ac016 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_unit.fixed @@ -0,0 +1,26 @@ +//run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::must_use_unit)] +#![allow(clippy::unused_unit)] + +#[macro_use] +extern crate macro_rules; + + +pub fn must_use_default() {} + + +pub fn must_use_unit() -> () {} + + +pub fn must_use_with_note() {} + +fn main() { + must_use_default(); + must_use_unit(); + must_use_with_note(); + + // We should not lint in external macros + must_use_unit!(); +} diff --git a/src/tools/clippy/tests/ui/must_use_unit.rs b/src/tools/clippy/tests/ui/must_use_unit.rs new file mode 100644 index 0000000000000..8a395dc284db4 --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_unit.rs @@ -0,0 +1,26 @@ +//run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::must_use_unit)] +#![allow(clippy::unused_unit)] + +#[macro_use] +extern crate macro_rules; + +#[must_use] +pub fn must_use_default() {} + +#[must_use] +pub fn must_use_unit() -> () {} + +#[must_use = "With note"] +pub fn must_use_with_note() {} + +fn main() { + must_use_default(); + must_use_unit(); + must_use_with_note(); + + // We should not lint in external macros + must_use_unit!(); +} diff --git a/src/tools/clippy/tests/ui/must_use_unit.stderr b/src/tools/clippy/tests/ui/must_use_unit.stderr new file mode 100644 index 0000000000000..15e0906b66b5e --- /dev/null +++ b/src/tools/clippy/tests/ui/must_use_unit.stderr @@ -0,0 +1,28 @@ +error: this unit-returning function has a `#[must_use]` attribute + --> $DIR/must_use_unit.rs:11:1 + | +LL | #[must_use] + | ----------- help: remove the attribute +LL | pub fn must_use_default() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::must-use-unit` implied by `-D warnings` + +error: this unit-returning function has a `#[must_use]` attribute + --> $DIR/must_use_unit.rs:14:1 + | +LL | #[must_use] + | ----------- help: remove the attribute +LL | pub fn must_use_unit() -> () {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this unit-returning function has a `#[must_use]` attribute + --> $DIR/must_use_unit.rs:17:1 + | +LL | #[must_use = "With note"] + | ------------------------- help: remove the attribute +LL | pub fn must_use_with_note() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_from_ref.rs b/src/tools/clippy/tests/ui/mut_from_ref.rs new file mode 100644 index 0000000000000..a9a04c8f56b94 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_from_ref.rs @@ -0,0 +1,46 @@ +#![allow(unused)] +#![warn(clippy::mut_from_ref)] + +struct Foo; + +impl Foo { + fn this_wont_hurt_a_bit(&self) -> &mut Foo { + unimplemented!() + } +} + +trait Ouch { + fn ouch(x: &Foo) -> &mut Foo; +} + +impl Ouch for Foo { + fn ouch(x: &Foo) -> &mut Foo { + unimplemented!() + } +} + +fn fail(x: &u32) -> &mut u16 { + unimplemented!() +} + +fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 { + unimplemented!() +} + +fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 { + unimplemented!() +} + +// this is OK, because the result borrows y +fn works<'a>(x: &u32, y: &'a mut u32) -> &'a mut u32 { + unimplemented!() +} + +// this is also OK, because the result could borrow y +fn also_works<'a>(x: &'a u32, y: &'a mut u32) -> &'a mut u32 { + unimplemented!() +} + +fn main() { + //TODO +} diff --git a/src/tools/clippy/tests/ui/mut_from_ref.stderr b/src/tools/clippy/tests/ui/mut_from_ref.stderr new file mode 100644 index 0000000000000..4787999920bc2 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_from_ref.stderr @@ -0,0 +1,63 @@ +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:7:39 + | +LL | fn this_wont_hurt_a_bit(&self) -> &mut Foo { + | ^^^^^^^^ + | + = note: `-D clippy::mut-from-ref` implied by `-D warnings` +note: immutable borrow here + --> $DIR/mut_from_ref.rs:7:29 + | +LL | fn this_wont_hurt_a_bit(&self) -> &mut Foo { + | ^^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:13:25 + | +LL | fn ouch(x: &Foo) -> &mut Foo; + | ^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:13:16 + | +LL | fn ouch(x: &Foo) -> &mut Foo; + | ^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:22:21 + | +LL | fn fail(x: &u32) -> &mut u16 { + | ^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:22:12 + | +LL | fn fail(x: &u32) -> &mut u16 { + | ^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:26:50 + | +LL | fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 { + | ^^^^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:26:25 + | +LL | fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 { + | ^^^^^^^ + +error: mutable borrow from immutable input(s) + --> $DIR/mut_from_ref.rs:30:67 + | +LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 { + | ^^^^^^^^^^^ + | +note: immutable borrow here + --> $DIR/mut_from_ref.rs:30:27 + | +LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 { + | ^^^^^^^ ^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_key.rs b/src/tools/clippy/tests/ui/mut_key.rs new file mode 100644 index 0000000000000..2d227e6654c36 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_key.rs @@ -0,0 +1,55 @@ +use std::collections::{HashMap, HashSet}; +use std::hash::{Hash, Hasher}; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +struct Key(AtomicUsize); + +impl Clone for Key { + fn clone(&self) -> Self { + Key(AtomicUsize::new(self.0.load(Relaxed))) + } +} + +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.0.load(Relaxed) == other.0.load(Relaxed) + } +} + +impl Eq for Key {} + +impl Hash for Key { + fn hash(&self, h: &mut H) { + self.0.load(Relaxed).hash(h); + } +} + +fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + let _other: HashMap = HashMap::new(); + m.keys().cloned().collect() +} + +fn this_is_ok(_m: &mut HashMap) {} + +#[allow(unused)] +trait Trait { + type AssociatedType; + + fn trait_fn(&self, set: std::collections::HashSet); +} + +fn generics_are_ok_too(_m: &mut HashSet) { + // nothing to see here, move along +} + +fn tuples(_m: &mut HashMap<((), U), ()>) {} + +fn tuples_bad(_m: &mut HashMap<(Key, U), bool>) {} + +fn main() { + let _ = should_not_take_this_arg(&mut HashMap::new(), 1); + this_is_ok(&mut HashMap::new()); + tuples::(&mut HashMap::new()); + tuples::<()>(&mut HashMap::new()); + tuples_bad::<()>(&mut HashMap::new()); +} diff --git a/src/tools/clippy/tests/ui/mut_key.stderr b/src/tools/clippy/tests/ui/mut_key.stderr new file mode 100644 index 0000000000000..8d6a259c7e385 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_key.stderr @@ -0,0 +1,28 @@ +error: mutable key type + --> $DIR/mut_key.rs:27:32 + | +LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::mutable_key_type)]` on by default + +error: mutable key type + --> $DIR/mut_key.rs:27:72 + | +LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + | ^^^^^^^^^^^^ + +error: mutable key type + --> $DIR/mut_key.rs:28:5 + | +LL | let _other: HashMap = HashMap::new(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: mutable key type + --> $DIR/mut_key.rs:47:22 + | +LL | fn tuples_bad(_m: &mut HashMap<(Key, U), bool>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_mut.rs b/src/tools/clippy/tests/ui/mut_mut.rs new file mode 100644 index 0000000000000..8965cef66dedd --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mut.rs @@ -0,0 +1,49 @@ +#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)] +#![warn(clippy::mut_mut)] + +fn fun(x: &mut &mut u32) -> bool { + **x > 0 +} + +fn less_fun(x: *mut *mut u32) { + let y = x; +} + +macro_rules! mut_ptr { + ($p:expr) => { + &mut $p + }; +} + +#[allow(unused_mut, unused_variables)] +fn main() { + let mut x = &mut &mut 1u32; + { + let mut y = &mut x; + } + + if fun(x) { + let y: &mut &mut u32 = &mut &mut 2; + **y + **x; + } + + if fun(x) { + let y: &mut &mut &mut u32 = &mut &mut &mut 2; + ***y + **x; + } + + let mut z = mut_ptr!(&mut 3u32); +} + +fn issue939() { + let array = [5, 6, 7, 8, 9]; + let mut args = array.iter().skip(2); + for &arg in &mut args { + println!("{}", arg); + } + + let args = &mut args; + for arg in args { + println!(":{}", arg); + } +} diff --git a/src/tools/clippy/tests/ui/mut_mut.stderr b/src/tools/clippy/tests/ui/mut_mut.stderr new file mode 100644 index 0000000000000..44e8142271418 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_mut.stderr @@ -0,0 +1,63 @@ +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:4:11 + | +LL | fn fun(x: &mut &mut u32) -> bool { + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::mut-mut` implied by `-D warnings` + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:20:17 + | +LL | let mut x = &mut &mut 1u32; + | ^^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:14:9 + | +LL | &mut $p + | ^^^^^^^ +... +LL | let mut z = mut_ptr!(&mut 3u32); + | ------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this expression mutably borrows a mutable reference. Consider reborrowing + --> $DIR/mut_mut.rs:22:21 + | +LL | let mut y = &mut x; + | ^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:26:32 + | +LL | let y: &mut &mut u32 = &mut &mut 2; + | ^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:26:16 + | +LL | let y: &mut &mut u32 = &mut &mut 2; + | ^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:31:37 + | +LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; + | ^^^^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:31:16 + | +LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; + | ^^^^^^^^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> $DIR/mut_mut.rs:31:21 + | +LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; + | ^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_range_bound.rs b/src/tools/clippy/tests/ui/mut_range_bound.rs new file mode 100644 index 0000000000000..1348dd2a3d8bb --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_range_bound.rs @@ -0,0 +1,63 @@ +#![allow(unused)] + +fn main() { + mut_range_bound_upper(); + mut_range_bound_lower(); + mut_range_bound_both(); + mut_range_bound_no_mutation(); + immut_range_bound(); + mut_borrow_range_bound(); + immut_borrow_range_bound(); +} + +fn mut_range_bound_upper() { + let mut m = 4; + for i in 0..m { + m = 5; + } // warning +} + +fn mut_range_bound_lower() { + let mut m = 4; + for i in m..10 { + m *= 2; + } // warning +} + +fn mut_range_bound_both() { + let mut m = 4; + let mut n = 6; + for i in m..n { + m = 5; + n = 7; + } // warning (1 for each mutated bound) +} + +fn mut_range_bound_no_mutation() { + let mut m = 4; + for i in 0..m { + continue; + } // no warning +} + +fn mut_borrow_range_bound() { + let mut m = 4; + for i in 0..m { + let n = &mut m; // warning + *n += 1; + } +} + +fn immut_borrow_range_bound() { + let mut m = 4; + for i in 0..m { + let n = &m; // should be no warning? + } +} + +fn immut_range_bound() { + let m = 4; + for i in 0..m { + continue; + } // no warning +} diff --git a/src/tools/clippy/tests/ui/mut_range_bound.stderr b/src/tools/clippy/tests/ui/mut_range_bound.stderr new file mode 100644 index 0000000000000..0eeb76e0ec5fd --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_range_bound.stderr @@ -0,0 +1,34 @@ +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:16:9 + | +LL | m = 5; + | ^ + | + = note: `-D clippy::mut-range-bound` implied by `-D warnings` + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:23:9 + | +LL | m *= 2; + | ^ + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:31:9 + | +LL | m = 5; + | ^ + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:32:9 + | +LL | n = 7; + | ^ + +error: attempt to mutate range bound within loop; note that the range of the loop is unchanged + --> $DIR/mut_range_bound.rs:46:22 + | +LL | let n = &mut m; // warning + | ^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/mut_reference.rs b/src/tools/clippy/tests/ui/mut_reference.rs new file mode 100644 index 0000000000000..73906121c402e --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_reference.rs @@ -0,0 +1,43 @@ +#![allow(unused_variables)] + +fn takes_an_immutable_reference(a: &i32) {} +fn takes_a_mutable_reference(a: &mut i32) {} + +struct MyStruct; + +impl MyStruct { + fn takes_an_immutable_reference(&self, a: &i32) {} + + fn takes_a_mutable_reference(&self, a: &mut i32) {} +} + +#[warn(clippy::unnecessary_mut_passed)] +fn main() { + // Functions + takes_an_immutable_reference(&mut 42); + let as_ptr: fn(&i32) = takes_an_immutable_reference; + as_ptr(&mut 42); + + // Methods + let my_struct = MyStruct; + my_struct.takes_an_immutable_reference(&mut 42); + + // No error + + // Functions + takes_an_immutable_reference(&42); + let as_ptr: fn(&i32) = takes_an_immutable_reference; + as_ptr(&42); + + takes_a_mutable_reference(&mut 42); + let as_ptr: fn(&mut i32) = takes_a_mutable_reference; + as_ptr(&mut 42); + + let a = &mut 42; + takes_an_immutable_reference(a); + + // Methods + my_struct.takes_an_immutable_reference(&42); + my_struct.takes_a_mutable_reference(&mut 42); + my_struct.takes_an_immutable_reference(a); +} diff --git a/src/tools/clippy/tests/ui/mut_reference.stderr b/src/tools/clippy/tests/ui/mut_reference.stderr new file mode 100644 index 0000000000000..fa8c82ae0f340 --- /dev/null +++ b/src/tools/clippy/tests/ui/mut_reference.stderr @@ -0,0 +1,22 @@ +error: The function/method `takes_an_immutable_reference` doesn't need a mutable reference + --> $DIR/mut_reference.rs:17:34 + | +LL | takes_an_immutable_reference(&mut 42); + | ^^^^^^^ + | + = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` + +error: The function/method `as_ptr` doesn't need a mutable reference + --> $DIR/mut_reference.rs:19:12 + | +LL | as_ptr(&mut 42); + | ^^^^^^^ + +error: The function/method `takes_an_immutable_reference` doesn't need a mutable reference + --> $DIR/mut_reference.rs:23:44 + | +LL | my_struct.takes_an_immutable_reference(&mut 42); + | ^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/mutex_atomic.rs b/src/tools/clippy/tests/ui/mutex_atomic.rs new file mode 100644 index 0000000000000..b9d78b7f47924 --- /dev/null +++ b/src/tools/clippy/tests/ui/mutex_atomic.rs @@ -0,0 +1,15 @@ +#![warn(clippy::all)] +#![warn(clippy::mutex_integer)] + +fn main() { + use std::sync::Mutex; + Mutex::new(true); + Mutex::new(5usize); + Mutex::new(9isize); + let mut x = 4u32; + Mutex::new(&x as *const u32); + Mutex::new(&mut x as *mut u32); + Mutex::new(0u32); + Mutex::new(0i32); + Mutex::new(0f32); // there are no float atomics, so this should not lint +} diff --git a/src/tools/clippy/tests/ui/mutex_atomic.stderr b/src/tools/clippy/tests/ui/mutex_atomic.stderr new file mode 100644 index 0000000000000..7dac086585548 --- /dev/null +++ b/src/tools/clippy/tests/ui/mutex_atomic.stderr @@ -0,0 +1,48 @@ +error: Consider using an `AtomicBool` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:6:5 + | +LL | Mutex::new(true); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mutex-atomic` implied by `-D warnings` + +error: Consider using an `AtomicUsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:7:5 + | +LL | Mutex::new(5usize); + | ^^^^^^^^^^^^^^^^^^ + +error: Consider using an `AtomicIsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:8:5 + | +LL | Mutex::new(9isize); + | ^^^^^^^^^^^^^^^^^^ + +error: Consider using an `AtomicPtr` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:10:5 + | +LL | Mutex::new(&x as *const u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Consider using an `AtomicPtr` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:11:5 + | +LL | Mutex::new(&mut x as *mut u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Consider using an `AtomicUsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:12:5 + | +LL | Mutex::new(0u32); + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mutex-integer` implied by `-D warnings` + +error: Consider using an `AtomicIsize` instead of a `Mutex` here. If you just want the locking behavior and not the internal type, consider using `Mutex<()>`. + --> $DIR/mutex_atomic.rs:13:5 + | +LL | Mutex::new(0i32); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.fixed b/src/tools/clippy/tests/ui/needless_bool/fixable.fixed new file mode 100644 index 0000000000000..567dbc54100a6 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.fixed @@ -0,0 +1,98 @@ +// run-rustfix + +#![warn(clippy::needless_bool)] +#![allow( + unused, + dead_code, + clippy::no_effect, + clippy::if_same_then_else, + clippy::needless_return +)] + +use std::cell::Cell; + +macro_rules! bool_comparison_trigger { + ($($i:ident: $def:expr, $stb:expr );+ $(;)*) => ( + + #[derive(Clone)] + pub struct Trigger { + $($i: (Cell, bool, bool)),+ + } + + #[allow(dead_code)] + impl Trigger { + pub fn trigger(&self, key: &str) -> bool { + $( + if let stringify!($i) = key { + return self.$i.1 && self.$i.2 == $def; + } + )+ + false + } + } + ) +} + +fn main() { + let x = true; + let y = false; + x; + !x; + !(x && y); + if x { + x + } else { + false + }; // would also be questionable, but we don't catch this yet + bool_ret3(x); + bool_ret4(x); + bool_ret5(x, x); + bool_ret6(x, x); + needless_bool(x); + needless_bool2(x); + needless_bool3(x); +} + +fn bool_ret3(x: bool) -> bool { + return x; +} + +fn bool_ret4(x: bool) -> bool { + return !x; +} + +fn bool_ret5(x: bool, y: bool) -> bool { + return x && y; +} + +fn bool_ret6(x: bool, y: bool) -> bool { + return !(x && y); +} + +fn needless_bool(x: bool) { + if x {}; +} + +fn needless_bool2(x: bool) { + if !x {}; +} + +fn needless_bool3(x: bool) { + bool_comparison_trigger! { + test_one: false, false; + test_three: false, false; + test_two: true, true; + } + + if x {}; + if !x {}; +} + +fn needless_bool_in_the_suggestion_wraps_the_predicate_of_if_else_statement_in_brackets() { + let b = false; + let returns_bool = || false; + + let x = if b { + true + } else { !returns_bool() }; +} diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.rs b/src/tools/clippy/tests/ui/needless_bool/fixable.rs new file mode 100644 index 0000000000000..10126ad4dbb15 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.rs @@ -0,0 +1,130 @@ +// run-rustfix + +#![warn(clippy::needless_bool)] +#![allow( + unused, + dead_code, + clippy::no_effect, + clippy::if_same_then_else, + clippy::needless_return +)] + +use std::cell::Cell; + +macro_rules! bool_comparison_trigger { + ($($i:ident: $def:expr, $stb:expr );+ $(;)*) => ( + + #[derive(Clone)] + pub struct Trigger { + $($i: (Cell, bool, bool)),+ + } + + #[allow(dead_code)] + impl Trigger { + pub fn trigger(&self, key: &str) -> bool { + $( + if let stringify!($i) = key { + return self.$i.1 && self.$i.2 == $def; + } + )+ + false + } + } + ) +} + +fn main() { + let x = true; + let y = false; + if x { + true + } else { + false + }; + if x { + false + } else { + true + }; + if x && y { + false + } else { + true + }; + if x { + x + } else { + false + }; // would also be questionable, but we don't catch this yet + bool_ret3(x); + bool_ret4(x); + bool_ret5(x, x); + bool_ret6(x, x); + needless_bool(x); + needless_bool2(x); + needless_bool3(x); +} + +fn bool_ret3(x: bool) -> bool { + if x { + return true; + } else { + return false; + }; +} + +fn bool_ret4(x: bool) -> bool { + if x { + return false; + } else { + return true; + }; +} + +fn bool_ret5(x: bool, y: bool) -> bool { + if x && y { + return true; + } else { + return false; + }; +} + +fn bool_ret6(x: bool, y: bool) -> bool { + if x && y { + return false; + } else { + return true; + }; +} + +fn needless_bool(x: bool) { + if x == true {}; +} + +fn needless_bool2(x: bool) { + if x == false {}; +} + +fn needless_bool3(x: bool) { + bool_comparison_trigger! { + test_one: false, false; + test_three: false, false; + test_two: true, true; + } + + if x == true {}; + if x == false {}; +} + +fn needless_bool_in_the_suggestion_wraps_the_predicate_of_if_else_statement_in_brackets() { + let b = false; + let returns_bool = || false; + + let x = if b { + true + } else if returns_bool() { + false + } else { + true + }; +} diff --git a/src/tools/clippy/tests/ui/needless_bool/fixable.stderr b/src/tools/clippy/tests/ui/needless_bool/fixable.stderr new file mode 100644 index 0000000000000..25abfb2a472b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/fixable.stderr @@ -0,0 +1,111 @@ +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:39:5 + | +LL | / if x { +LL | | true +LL | | } else { +LL | | false +LL | | }; + | |_____^ help: you can reduce it to: `x` + | + = note: `-D clippy::needless-bool` implied by `-D warnings` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:44:5 + | +LL | / if x { +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ help: you can reduce it to: `!x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:49:5 + | +LL | / if x && y { +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ help: you can reduce it to: `!(x && y)` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:69:5 + | +LL | / if x { +LL | | return true; +LL | | } else { +LL | | return false; +LL | | }; + | |_____^ help: you can reduce it to: `return x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:77:5 + | +LL | / if x { +LL | | return false; +LL | | } else { +LL | | return true; +LL | | }; + | |_____^ help: you can reduce it to: `return !x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:85:5 + | +LL | / if x && y { +LL | | return true; +LL | | } else { +LL | | return false; +LL | | }; + | |_____^ help: you can reduce it to: `return x && y` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:93:5 + | +LL | / if x && y { +LL | | return false; +LL | | } else { +LL | | return true; +LL | | }; + | |_____^ help: you can reduce it to: `return !(x && y)` + +error: equality checks against true are unnecessary + --> $DIR/fixable.rs:101:8 + | +LL | if x == true {}; + | ^^^^^^^^^ help: try simplifying it as shown: `x` + | + = note: `-D clippy::bool-comparison` implied by `-D warnings` + +error: equality checks against false can be replaced by a negation + --> $DIR/fixable.rs:105:8 + | +LL | if x == false {}; + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: equality checks against true are unnecessary + --> $DIR/fixable.rs:115:8 + | +LL | if x == true {}; + | ^^^^^^^^^ help: try simplifying it as shown: `x` + +error: equality checks against false can be replaced by a negation + --> $DIR/fixable.rs:116:8 + | +LL | if x == false {}; + | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + +error: this if-then-else expression returns a bool literal + --> $DIR/fixable.rs:125:12 + | +LL | } else if returns_bool() { + | ____________^ +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ help: you can reduce it to: `{ !returns_bool() }` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_bool/simple.rs b/src/tools/clippy/tests/ui/needless_bool/simple.rs new file mode 100644 index 0000000000000..e9f1428fc3a43 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/simple.rs @@ -0,0 +1,46 @@ +#![warn(clippy::needless_bool)] +#![allow( + unused, + dead_code, + clippy::no_effect, + clippy::if_same_then_else, + clippy::needless_return +)] + +fn main() { + let x = true; + let y = false; + if x { + true + } else { + true + }; + if x { + false + } else { + false + }; + if x { + x + } else { + false + }; // would also be questionable, but we don't catch this yet + bool_ret(x); + bool_ret2(x); +} + +fn bool_ret(x: bool) -> bool { + if x { + return true; + } else { + return true; + }; +} + +fn bool_ret2(x: bool) -> bool { + if x { + return false; + } else { + return false; + }; +} diff --git a/src/tools/clippy/tests/ui/needless_bool/simple.stderr b/src/tools/clippy/tests/ui/needless_bool/simple.stderr new file mode 100644 index 0000000000000..c57a8a042fb88 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_bool/simple.stderr @@ -0,0 +1,44 @@ +error: this if-then-else expression will always return true + --> $DIR/simple.rs:13:5 + | +LL | / if x { +LL | | true +LL | | } else { +LL | | true +LL | | }; + | |_____^ + | + = note: `-D clippy::needless-bool` implied by `-D warnings` + +error: this if-then-else expression will always return false + --> $DIR/simple.rs:18:5 + | +LL | / if x { +LL | | false +LL | | } else { +LL | | false +LL | | }; + | |_____^ + +error: this if-then-else expression will always return true + --> $DIR/simple.rs:33:5 + | +LL | / if x { +LL | | return true; +LL | | } else { +LL | | return true; +LL | | }; + | |_____^ + +error: this if-then-else expression will always return false + --> $DIR/simple.rs:41:5 + | +LL | / if x { +LL | | return false; +LL | | } else { +LL | | return false; +LL | | }; + | |_____^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_borrow.fixed b/src/tools/clippy/tests/ui/needless_borrow.fixed new file mode 100644 index 0000000000000..5ae4a0e79b99d --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrow.fixed @@ -0,0 +1,61 @@ +// run-rustfix + +#![allow(clippy::needless_borrowed_reference)] + +fn x(y: &i32) -> i32 { + *y +} + +#[warn(clippy::all, clippy::needless_borrow)] +#[allow(unused_variables)] +fn main() { + let a = 5; + let b = x(&a); + let c = x(&a); + let s = &String::from("hi"); + let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not + let g_val = g(&Vec::new()); // should not error, because `&Vec` derefs to `&[T]` + let vec = Vec::new(); + let vec_val = g(&vec); // should not error, because `&Vec` derefs to `&[T]` + h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait` + if let Some(cake) = Some(&5) {} + let garbl = match 42 { + 44 => &a, + 45 => { + println!("foo"); + &&a // FIXME: this should lint, too + }, + 46 => &a, + _ => panic!(), + }; +} + +fn f(y: &T) -> T { + *y +} + +fn g(y: &[u8]) -> u8 { + y[0] +} + +trait Trait {} + +impl<'a> Trait for &'a str {} + +fn h(_: &dyn Trait) {} +#[warn(clippy::needless_borrow)] +#[allow(dead_code)] +fn issue_1432() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + let _ = v.iter().filter(|&a| a.is_empty()); + + let _ = v.iter().filter(|&a| a.is_empty()); +} + +#[allow(dead_code)] +#[warn(clippy::needless_borrow)] +#[derive(Debug)] +enum Foo<'a> { + Str(&'a str), +} diff --git a/src/tools/clippy/tests/ui/needless_borrow.rs b/src/tools/clippy/tests/ui/needless_borrow.rs new file mode 100644 index 0000000000000..1e281316c8a39 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrow.rs @@ -0,0 +1,61 @@ +// run-rustfix + +#![allow(clippy::needless_borrowed_reference)] + +fn x(y: &i32) -> i32 { + *y +} + +#[warn(clippy::all, clippy::needless_borrow)] +#[allow(unused_variables)] +fn main() { + let a = 5; + let b = x(&a); + let c = x(&&a); + let s = &String::from("hi"); + let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not + let g_val = g(&Vec::new()); // should not error, because `&Vec` derefs to `&[T]` + let vec = Vec::new(); + let vec_val = g(&vec); // should not error, because `&Vec` derefs to `&[T]` + h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait` + if let Some(ref cake) = Some(&5) {} + let garbl = match 42 { + 44 => &a, + 45 => { + println!("foo"); + &&a // FIXME: this should lint, too + }, + 46 => &&a, + _ => panic!(), + }; +} + +fn f(y: &T) -> T { + *y +} + +fn g(y: &[u8]) -> u8 { + y[0] +} + +trait Trait {} + +impl<'a> Trait for &'a str {} + +fn h(_: &dyn Trait) {} +#[warn(clippy::needless_borrow)] +#[allow(dead_code)] +fn issue_1432() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + let _ = v.iter().filter(|&ref a| a.is_empty()); + + let _ = v.iter().filter(|&a| a.is_empty()); +} + +#[allow(dead_code)] +#[warn(clippy::needless_borrow)] +#[derive(Debug)] +enum Foo<'a> { + Str(&'a str), +} diff --git a/src/tools/clippy/tests/ui/needless_borrow.stderr b/src/tools/clippy/tests/ui/needless_borrow.stderr new file mode 100644 index 0000000000000..0bfeda7914db7 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrow.stderr @@ -0,0 +1,28 @@ +error: this expression borrows a reference that is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:14:15 + | +LL | let c = x(&&a); + | ^^^ help: change this to: `&a` + | + = note: `-D clippy::needless-borrow` implied by `-D warnings` + +error: this pattern creates a reference to a reference + --> $DIR/needless_borrow.rs:21:17 + | +LL | if let Some(ref cake) = Some(&5) {} + | ^^^^^^^^ help: change this to: `cake` + +error: this expression borrows a reference that is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:28:15 + | +LL | 46 => &&a, + | ^^^ help: change this to: `&a` + +error: this pattern creates a reference to a reference + --> $DIR/needless_borrow.rs:51:31 + | +LL | let _ = v.iter().filter(|&ref a| a.is_empty()); + | ^^^^^ help: change this to: `a` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_borrowed_ref.fixed b/src/tools/clippy/tests/ui/needless_borrowed_ref.fixed new file mode 100644 index 0000000000000..a0937a2c5f62f --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrowed_ref.fixed @@ -0,0 +1,45 @@ +// run-rustfix + +#[warn(clippy::needless_borrowed_reference)] +#[allow(unused_variables)] +fn main() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|a| a.is_empty()); + // ^ should be linted + + let var = 3; + let thingy = Some(&var); + if let Some(&ref v) = thingy { + // ^ should be linted + } + + let mut var2 = 5; + let thingy2 = Some(&mut var2); + if let Some(&mut ref mut v) = thingy2 { + // ^ should **not** be linted + // v is borrowed as mutable. + *v = 10; + } + if let Some(&mut ref v) = thingy2 { + // ^ should **not** be linted + // here, v is borrowed as immutable. + // can't do that: + //*v = 15; + } +} + +#[allow(dead_code)] +enum Animal { + Cat(u64), + Dog(u64), +} + +#[allow(unused_variables)] +#[allow(dead_code)] +fn foo(a: &Animal, b: &Animal) { + match (a, b) { + (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref' + // ^ and ^ should **not** be linted + (&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted + } +} diff --git a/src/tools/clippy/tests/ui/needless_borrowed_ref.rs b/src/tools/clippy/tests/ui/needless_borrowed_ref.rs new file mode 100644 index 0000000000000..500ac448f0d58 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrowed_ref.rs @@ -0,0 +1,45 @@ +// run-rustfix + +#[warn(clippy::needless_borrowed_reference)] +#[allow(unused_variables)] +fn main() { + let mut v = Vec::::new(); + let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + // ^ should be linted + + let var = 3; + let thingy = Some(&var); + if let Some(&ref v) = thingy { + // ^ should be linted + } + + let mut var2 = 5; + let thingy2 = Some(&mut var2); + if let Some(&mut ref mut v) = thingy2 { + // ^ should **not** be linted + // v is borrowed as mutable. + *v = 10; + } + if let Some(&mut ref v) = thingy2 { + // ^ should **not** be linted + // here, v is borrowed as immutable. + // can't do that: + //*v = 15; + } +} + +#[allow(dead_code)] +enum Animal { + Cat(u64), + Dog(u64), +} + +#[allow(unused_variables)] +#[allow(dead_code)] +fn foo(a: &Animal, b: &Animal) { + match (a, b) { + (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref' + // ^ and ^ should **not** be linted + (&Animal::Dog(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted + } +} diff --git a/src/tools/clippy/tests/ui/needless_borrowed_ref.stderr b/src/tools/clippy/tests/ui/needless_borrowed_ref.stderr new file mode 100644 index 0000000000000..0a5cfb3db0b11 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_borrowed_ref.stderr @@ -0,0 +1,10 @@ +error: this pattern takes a reference on something that is being de-referenced + --> $DIR/needless_borrowed_ref.rs:7:34 + | +LL | let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + | ^^^^^^ help: try removing the `&ref` part and just keep: `a` + | + = note: `-D clippy::needless-borrowed-reference` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/needless_collect.fixed b/src/tools/clippy/tests/ui/needless_collect.fixed new file mode 100644 index 0000000000000..b4227eaf2f8bb --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect.fixed @@ -0,0 +1,21 @@ +// run-rustfix + +#![allow(unused, clippy::suspicious_map)] + +use std::collections::{BTreeSet, HashMap, HashSet}; + +#[warn(clippy::needless_collect)] +#[allow(unused_variables, clippy::iter_cloned_collect)] +fn main() { + let sample = [1; 5]; + let len = sample.iter().count(); + if sample.iter().next().is_none() { + // Empty + } + sample.iter().cloned().any(|x| x == 1); + sample.iter().map(|x| (x, x)).count(); + // Notice the `HashSet`--this should not be linted + sample.iter().collect::>().len(); + // Neither should this + sample.iter().collect::>().len(); +} diff --git a/src/tools/clippy/tests/ui/needless_collect.rs b/src/tools/clippy/tests/ui/needless_collect.rs new file mode 100644 index 0000000000000..7ee603afeb077 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect.rs @@ -0,0 +1,21 @@ +// run-rustfix + +#![allow(unused, clippy::suspicious_map)] + +use std::collections::{BTreeSet, HashMap, HashSet}; + +#[warn(clippy::needless_collect)] +#[allow(unused_variables, clippy::iter_cloned_collect)] +fn main() { + let sample = [1; 5]; + let len = sample.iter().collect::>().len(); + if sample.iter().collect::>().is_empty() { + // Empty + } + sample.iter().cloned().collect::>().contains(&1); + sample.iter().map(|x| (x, x)).collect::>().len(); + // Notice the `HashSet`--this should not be linted + sample.iter().collect::>().len(); + // Neither should this + sample.iter().collect::>().len(); +} diff --git a/src/tools/clippy/tests/ui/needless_collect.stderr b/src/tools/clippy/tests/ui/needless_collect.stderr new file mode 100644 index 0000000000000..8884c8e161293 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_collect.stderr @@ -0,0 +1,28 @@ +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:11:28 + | +LL | let len = sample.iter().collect::>().len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` + | + = note: `-D clippy::needless-collect` implied by `-D warnings` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:12:21 + | +LL | if sample.iter().collect::>().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.next().is_none()` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:15:27 + | +LL | sample.iter().cloned().collect::>().contains(&1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.any(|x| x == 1)` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:16:34 + | +LL | sample.iter().map(|x| (x, x)).collect::>().len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_continue.rs b/src/tools/clippy/tests/ui/needless_continue.rs new file mode 100644 index 0000000000000..5da95647f2c15 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_continue.rs @@ -0,0 +1,115 @@ +#![warn(clippy::needless_continue)] + +macro_rules! zero { + ($x:expr) => { + $x == 0 + }; +} + +macro_rules! nonzero { + ($x:expr) => { + !zero!($x) + }; +} + +fn main() { + let mut i = 1; + while i < 10 { + i += 1; + + if i % 2 == 0 && i % 3 == 0 { + println!("{}", i); + println!("{}", i + 1); + if i % 5 == 0 { + println!("{}", i + 2); + } + let i = 0; + println!("bar {} ", i); + } else { + continue; + } + + println!("bleh"); + { + println!("blah"); + } + + // some comments that also should ideally be included in the + // output of the lint suggestion if possible. + if !(!(i == 2) || !(i == 5)) { + println!("lama"); + } + + if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { + continue; + } else { + println!("Blabber"); + println!("Jabber"); + } + + println!("bleh"); + } +} + +mod issue_2329 { + fn condition() -> bool { + unimplemented!() + } + fn update_condition() {} + + // only the outer loop has a label + fn foo() { + 'outer: loop { + println!("Entry"); + while condition() { + update_condition(); + if condition() { + println!("foo-1"); + } else { + continue 'outer; // should not lint here + } + println!("foo-2"); + + update_condition(); + if condition() { + continue 'outer; // should not lint here + } else { + println!("foo-3"); + } + println!("foo-4"); + } + } + } + + // both loops have labels + fn bar() { + 'outer: loop { + println!("Entry"); + 'inner: while condition() { + update_condition(); + if condition() { + println!("bar-1"); + } else { + continue 'outer; // should not lint here + } + println!("bar-2"); + + update_condition(); + if condition() { + println!("bar-3"); + } else { + continue 'inner; // should lint here + } + println!("bar-4"); + + update_condition(); + if condition() { + continue; // should lint here + } else { + println!("bar-5"); + } + println!("bar-6"); + } + } + } +} diff --git a/src/tools/clippy/tests/ui/needless_continue.stderr b/src/tools/clippy/tests/ui/needless_continue.stderr new file mode 100644 index 0000000000000..8d6a37df9601a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_continue.stderr @@ -0,0 +1,99 @@ +error: this `else` block is redundant + --> $DIR/needless_continue.rs:28:16 + | +LL | } else { + | ________________^ +LL | | continue; +LL | | } + | |_________^ + | + = note: `-D clippy::needless-continue` implied by `-D warnings` + = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block + if i % 2 == 0 && i % 3 == 0 { + println!("{}", i); + println!("{}", i + 1); + if i % 5 == 0 { + println!("{}", i + 2); + } + let i = 0; + println!("bar {} ", i); + // merged code follows: + println!("bleh"); + { + println!("blah"); + } + if !(!(i == 2) || !(i == 5)) { + println!("lama"); + } + if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { + continue; + } else { + println!("Blabber"); + println!("Jabber"); + } + println!("bleh"); + } + +error: there is no need for an explicit `else` block for this `if` expression + --> $DIR/needless_continue.rs:43:9 + | +LL | / if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { +LL | | continue; +LL | | } else { +LL | | println!("Blabber"); +LL | | println!("Jabber"); +LL | | } + | |_________^ + | + = help: consider dropping the `else` clause + if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 { + continue; + } + { + println!("Blabber"); + println!("Jabber"); + } + +error: this `else` block is redundant + --> $DIR/needless_continue.rs:100:24 + | +LL | } else { + | ________________________^ +LL | | continue 'inner; // should lint here +LL | | } + | |_________________^ + | + = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block + if condition() { + println!("bar-3"); + // merged code follows: + println!("bar-4"); + update_condition(); + if condition() { + continue; // should lint here + } else { + println!("bar-5"); + } + println!("bar-6"); + } + +error: there is no need for an explicit `else` block for this `if` expression + --> $DIR/needless_continue.rs:106:17 + | +LL | / if condition() { +LL | | continue; // should lint here +LL | | } else { +LL | | println!("bar-5"); +LL | | } + | |_________________^ + | + = help: consider dropping the `else` clause + if condition() { + continue; // should lint here + } + { + println!("bar-5"); + } + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_doc_main.rs b/src/tools/clippy/tests/ui/needless_doc_main.rs new file mode 100644 index 0000000000000..682d7b3c4ceb4 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_doc_main.rs @@ -0,0 +1,74 @@ +/// This is a test for needless `fn main()` in doctests. +/// +/// # Examples +/// +/// This should lint +/// ``` +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// This should, too. +/// +/// ```rust +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// This one too. +/// +/// ```no_run +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +fn bad_doctests() {} + +/// # Examples +/// +/// This shouldn't lint, because the `main` is empty: +/// ``` +/// fn main(){} +/// ``` +/// +/// This shouldn't lint either, because there's a `static`: +/// ``` +/// static ANSWER: i32 = 42; +/// +/// fn main() { +/// assert_eq!(42, ANSWER); +/// } +/// ``` +/// +/// Neither should this lint because of `extern crate`: +/// ``` +/// #![feature(test)] +/// extern crate test; +/// fn main() { +/// assert_eq(1u8, test::black_box(1)); +/// } +/// ``` +/// +/// We should not lint ignored examples: +/// +/// ```rust,ignore +/// fn main() { +/// unimplemented!(); +/// } +/// ``` +/// +/// Or even non-rust examples: +/// +/// ```text +/// fn main() { +/// is what starts the program +/// } +/// ``` +fn no_false_positives() {} + +fn main() { + bad_doctests(); + no_false_positives(); +} diff --git a/src/tools/clippy/tests/ui/needless_doc_main.stderr b/src/tools/clippy/tests/ui/needless_doc_main.stderr new file mode 100644 index 0000000000000..65d40ee6832f2 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_doc_main.stderr @@ -0,0 +1,22 @@ +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:7:4 + | +LL | /// fn main() { + | ^^^^^^^^^^^^ + | + = note: `-D clippy::needless-doctest-main` implied by `-D warnings` + +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:15:4 + | +LL | /// fn main() { + | ^^^^^^^^^^^^ + +error: needless `fn main` in doctest + --> $DIR/needless_doc_main.rs:23:4 + | +LL | /// fn main() { + | ^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.rs b/src/tools/clippy/tests/ui/needless_lifetimes.rs new file mode 100644 index 0000000000000..913cd004f19f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_lifetimes.rs @@ -0,0 +1,262 @@ +#![warn(clippy::needless_lifetimes)] +#![allow(dead_code, clippy::needless_pass_by_value)] + +fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} + +fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} + +// No error; same lifetime on two params. +fn same_lifetime_on_input<'a>(_x: &'a u8, _y: &'a u8) {} + +// No error; static involved. +fn only_static_on_input(_x: &u8, _y: &u8, _z: &'static u8) {} + +fn mut_and_static_input(_x: &mut u8, _y: &'static str) {} + +fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { + x +} + +// No error; multiple input refs. +fn multiple_in_and_out_1<'a>(x: &'a u8, _y: &'a u8) -> &'a u8 { + x +} + +// No error; multiple input refs. +fn multiple_in_and_out_2<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 { + x +} + +// No error; static involved. +fn in_static_and_out<'a>(x: &'a u8, _y: &'static u8) -> &'a u8 { + x +} + +// No error. +fn deep_reference_1<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> { + Ok(x) +} + +// No error; two input refs. +fn deep_reference_2<'a>(x: Result<&'a u8, &'a u8>) -> &'a u8 { + x.unwrap() +} + +fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { + Ok(x) +} + +// Where-clause, but without lifetimes. +fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> +where + T: Copy, +{ + Ok(x) +} + +type Ref<'r> = &'r u8; + +// No error; same lifetime on two params. +fn lifetime_param_1<'a>(_x: Ref<'a>, _y: &'a u8) {} + +fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} + +// No error; bounded lifetime. +fn lifetime_param_3<'a, 'b: 'a>(_x: Ref<'a>, _y: &'b u8) {} + +// No error; bounded lifetime. +fn lifetime_param_4<'a, 'b>(_x: Ref<'a>, _y: &'b u8) +where + 'b: 'a, +{ +} + +struct Lt<'a, I: 'static> { + x: &'a I, +} + +// No error; fn bound references `'a`. +fn fn_bound<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> +where + F: Fn(Lt<'a, I>) -> Lt<'a, I>, +{ + unreachable!() +} + +fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> +where + for<'x> F: Fn(Lt<'x, I>) -> Lt<'x, I>, +{ + unreachable!() +} + +// No error; see below. +fn fn_bound_3<'a, F: FnOnce(&'a i32)>(x: &'a i32, f: F) { + f(x); +} + +fn fn_bound_3_cannot_elide() { + let x = 42; + let p = &x; + let mut q = &x; + // This will fail if we elide lifetimes of `fn_bound_3`. + fn_bound_3(p, |y| q = y); +} + +// No error; multiple input refs. +fn fn_bound_4<'a, F: FnOnce() -> &'a ()>(cond: bool, x: &'a (), f: F) -> &'a () { + if cond { + x + } else { + f() + } +} + +struct X { + x: u8, +} + +impl X { + fn self_and_out<'s>(&'s self) -> &'s u8 { + &self.x + } + + // No error; multiple input refs. + fn self_and_in_out<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 { + &self.x + } + + fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} + + // No error; same lifetimes on two params. + fn self_and_same_in<'s>(&'s self, _x: &'s u8) {} +} + +struct Foo<'a>(&'a u8); + +impl<'a> Foo<'a> { + // No error; lifetime `'a` not defined in method. + fn self_shared_lifetime(&self, _: &'a u8) {} + // No error; bounds exist. + fn self_bound_lifetime<'b: 'a>(&self, _: &'b u8) {} +} + +fn already_elided<'a>(_: &u8, _: &'a u8) -> &'a u8 { + unimplemented!() +} + +fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (named on the reference, anonymous on `Foo`). +fn struct_with_lt2<'a>(_foo: &'a Foo) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (anonymous on the reference, named on `Foo`). +fn struct_with_lt3<'a>(_foo: &Foo<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes. +fn struct_with_lt4<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str { + unimplemented!() +} + +trait WithLifetime<'a> {} + +type WithLifetimeAlias<'a> = dyn WithLifetime<'a>; + +// Should not warn because it won't build without the lifetime. +fn trait_obj_elided<'a>(_arg: &'a dyn WithLifetime) -> &'a str { + unimplemented!() +} + +// Should warn because there is no lifetime on `Drop`, so this would be +// unambiguous if we elided the lifetime. +fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { + unimplemented!() +} + +type FooAlias<'a> = Foo<'a>; + +fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (named on the reference, anonymous on `FooAlias`). +fn alias_with_lt2<'a>(_foo: &'a FooAlias) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (anonymous on the reference, named on `FooAlias`). +fn alias_with_lt3<'a>(_foo: &FooAlias<'a>) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes. +fn alias_with_lt4<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str { + unimplemented!() +} + +fn named_input_elided_output<'a>(_arg: &'a str) -> &str { + unimplemented!() +} + +fn elided_input_named_output<'a>(_arg: &str) -> &'a str { + unimplemented!() +} + +fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { + unimplemented!() +} +fn trait_bound<'a, T: WithLifetime<'a>>(_: &'a u8, _: T) { + unimplemented!() +} + +// Don't warn on these; see issue #292. +fn trait_bound_bug<'a, T: WithLifetime<'a>>() { + unimplemented!() +} + +// See issue #740. +struct Test { + vec: Vec, +} + +impl Test { + fn iter<'a>(&'a self) -> Box + 'a> { + unimplemented!() + } +} + +trait LintContext<'a> {} + +fn f<'a, T: LintContext<'a>>(_: &T) {} + +fn test<'a>(x: &'a [u8]) -> u8 { + let y: &'a u8 = &x[5]; + *y +} + +// Issue #3284: give hint regarding lifetime in return type. +struct Cow<'a> { + x: &'a str, +} +fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { + unimplemented!() +} + +// Make sure we still warn on implementations +mod issue4291 { + trait BadTrait { + fn needless_lt<'a>(x: &'a u8) {} + } + + impl BadTrait for () { + fn needless_lt<'a>(_x: &'a u8) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.stderr b/src/tools/clippy/tests/ui/needless_lifetimes.stderr new file mode 100644 index 0000000000000..d3a360ed8b576 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_lifetimes.stderr @@ -0,0 +1,106 @@ +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:4:1 + | +LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::needless-lifetimes` implied by `-D warnings` + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:6:1 + | +LL | fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:16:1 + | +LL | fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:45:1 + | +LL | fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:50:1 + | +LL | fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:62:1 + | +LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:86:1 + | +LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:120:5 + | +LL | fn self_and_out<'s>(&'s self) -> &'s u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:129:5 + | +LL | fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:148:1 + | +LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:178:1 + | +LL | fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:184:1 + | +LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:203:1 + | +LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:211:1 + | +LL | fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:247:1 + | +LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:254:9 + | +LL | fn needless_lt<'a>(x: &'a u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:258:9 + | +LL | fn needless_lt<'a>(_x: &'a u8) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_pass_by_value.rs b/src/tools/clippy/tests/ui/needless_pass_by_value.rs new file mode 100644 index 0000000000000..e93a7fe2985b3 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_pass_by_value.rs @@ -0,0 +1,161 @@ +#![warn(clippy::needless_pass_by_value)] +#![allow( + dead_code, + clippy::single_match, + clippy::redundant_pattern_matching, + clippy::many_single_char_names, + clippy::option_option, + clippy::redundant_clone +)] + +use std::borrow::Borrow; +use std::collections::HashSet; +use std::convert::AsRef; +use std::mem::MaybeUninit; + +// `v` should be warned +// `w`, `x` and `y` are allowed (moved or mutated) +fn foo(v: Vec, w: Vec, mut x: Vec, y: Vec) -> Vec { + assert_eq!(v.len(), 42); + + consume(w); + + x.push(T::default()); + + y +} + +fn consume(_: T) {} + +struct Wrapper(String); + +fn bar(x: String, y: Wrapper) { + assert_eq!(x.len(), 42); + assert_eq!(y.0.len(), 42); +} + +// V implements `Borrow`, but should be warned correctly +fn test_borrow_trait, U: AsRef, V>(t: T, u: U, v: V) { + println!("{}", t.borrow()); + println!("{}", u.as_ref()); + consume(&v); +} + +// ok +fn test_fn i32>(f: F) { + f(1); +} + +// x should be warned, but y is ok +fn test_match(x: Option>, y: Option>) { + match x { + Some(Some(_)) => 1, // not moved + _ => 0, + }; + + match y { + Some(Some(s)) => consume(s), // moved + _ => (), + }; +} + +// x and y should be warned, but z is ok +fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) { + let Wrapper(s) = z; // moved + let Wrapper(ref t) = y; // not moved + let Wrapper(_) = y; // still not moved + + assert_eq!(x.0.len(), s.len()); + println!("{}", t); +} + +trait Foo {} + +// `S: Serialize` is allowed to be passed by value, since a caller can pass `&S` instead +trait Serialize {} +impl<'a, T> Serialize for &'a T where T: Serialize {} +impl Serialize for i32 {} + +fn test_blanket_ref(_foo: T, _serializable: S) {} + +fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + s.capacity(); + let _ = t.clone(); + u.capacity(); + let _ = v.clone(); +} + +struct S(T, U); + +impl S { + fn foo( + self, + // taking `self` by value is always allowed + s: String, + t: String, + ) -> usize { + s.len() + t.capacity() + } + + fn bar(_t: T, // Ok, since `&T: Serialize` too + ) { + } + + fn baz(&self, _u: U, _s: Self) {} +} + +trait FalsePositive { + fn visit_str(s: &str); + fn visit_string(s: String) { + Self::visit_str(&s); + } +} + +// shouldn't warn on extern funcs +extern "C" fn ext(x: MaybeUninit) -> usize { + unsafe { x.assume_init() } +} + +// whitelist RangeArgument +fn range>(range: T) { + let _ = range.start_bound(); +} + +struct CopyWrapper(u32); + +fn bar_copy(x: u32, y: CopyWrapper) { + assert_eq!(x, 42); + assert_eq!(y.0, 42); +} + +// x and y should be warned, but z is ok +fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + let CopyWrapper(s) = z; // moved + let CopyWrapper(ref t) = y; // not moved + let CopyWrapper(_) = y; // still not moved + + assert_eq!(x.0, s); + println!("{}", t); +} + +// The following 3 lines should not cause an ICE. See #2831 +trait Bar<'a, A> {} +impl<'b, T> Bar<'b, T> for T {} +fn some_fun<'b, S: Bar<'b, ()>>(_item: S) {} + +// Also this should not cause an ICE. See #2831 +trait Club<'a, A> {} +impl Club<'static, T> for T {} +fn more_fun(_item: impl Club<'static, i32>) {} + +fn is_sync(_: T) +where + T: Sync, +{ +} + +fn main() { + // This should not cause an ICE either + // https://github.com/rust-lang/rust-clippy/issues/3144 + is_sync(HashSet::::new()); +} diff --git a/src/tools/clippy/tests/ui/needless_pass_by_value.stderr b/src/tools/clippy/tests/ui/needless_pass_by_value.stderr new file mode 100644 index 0000000000000..9aa783bf904e1 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_pass_by_value.stderr @@ -0,0 +1,178 @@ +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:18:23 + | +LL | fn foo(v: Vec, w: Vec, mut x: Vec, y: Vec) -> Vec { + | ^^^^^^ help: consider changing the type to: `&[T]` + | + = note: `-D clippy::needless-pass-by-value` implied by `-D warnings` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:32:11 + | +LL | fn bar(x: String, y: Wrapper) { + | ^^^^^^ help: consider changing the type to: `&str` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:32:22 + | +LL | fn bar(x: String, y: Wrapper) { + | ^^^^^^^ help: consider taking a reference instead: `&Wrapper` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:38:71 + | +LL | fn test_borrow_trait, U: AsRef, V>(t: T, u: U, v: V) { + | ^ help: consider taking a reference instead: `&V` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:50:18 + | +LL | fn test_match(x: Option>, y: Option>) { + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&Option>` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:63:24 + | +LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) { + | ^^^^^^^ help: consider taking a reference instead: `&Wrapper` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:63:36 + | +LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) { + | ^^^^^^^ help: consider taking a reference instead: `&Wrapper` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:79:49 + | +LL | fn test_blanket_ref(_foo: T, _serializable: S) {} + | ^ help: consider taking a reference instead: `&T` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:18 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^ help: consider taking a reference instead: `&String` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:29 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^ + | +help: consider changing the type to + | +LL | fn issue_2114(s: String, t: &str, u: Vec, v: Vec) { + | ^^^^ +help: change `t.clone()` to + | +LL | let _ = t.to_string(); + | ^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:40 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^^^ help: consider taking a reference instead: `&Vec` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:81:53 + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: Vec) { + | ^^^^^^^^ + | +help: consider changing the type to + | +LL | fn issue_2114(s: String, t: String, u: Vec, v: &[i32]) { + | ^^^^^^ +help: change `v.clone()` to + | +LL | let _ = v.to_owned(); + | ^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:94:12 + | +LL | s: String, + | ^^^^^^ help: consider changing the type to: `&str` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:95:12 + | +LL | t: String, + | ^^^^^^ help: consider taking a reference instead: `&String` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:104:23 + | +LL | fn baz(&self, _u: U, _s: Self) {} + | ^ help: consider taking a reference instead: `&U` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:104:30 + | +LL | fn baz(&self, _u: U, _s: Self) {} + | ^^^^ help: consider taking a reference instead: `&Self` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:126:24 + | +LL | fn bar_copy(x: u32, y: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:132:29 + | +LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:132:45 + | +LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:132:61 + | +LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) { + | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value.rs:124:1 + | +LL | struct CopyWrapper(u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:144:40 + | +LL | fn some_fun<'b, S: Bar<'b, ()>>(_item: S) {} + | ^ help: consider taking a reference instead: `&S` + +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value.rs:149:20 + | +LL | fn more_fun(_item: impl Club<'static, i32>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&impl Club<'static, i32>` + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_pass_by_value_proc_macro.rs b/src/tools/clippy/tests/ui/needless_pass_by_value_proc_macro.rs new file mode 100644 index 0000000000000..78a0e92d17979 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_pass_by_value_proc_macro.rs @@ -0,0 +1,21 @@ +#![crate_type = "proc-macro"] +#![warn(clippy::needless_pass_by_value)] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_derive(Foo)] +pub fn foo(_input: TokenStream) -> TokenStream { + unimplemented!() +} + +#[proc_macro] +pub fn bar(_input: TokenStream) -> TokenStream { + unimplemented!() +} + +#[proc_macro_attribute] +pub fn baz(_args: TokenStream, _input: TokenStream) -> TokenStream { + unimplemented!() +} diff --git a/src/tools/clippy/tests/ui/needless_range_loop.rs b/src/tools/clippy/tests/ui/needless_range_loop.rs new file mode 100644 index 0000000000000..3fce34367ae50 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop.rs @@ -0,0 +1,95 @@ +#![warn(clippy::needless_range_loop)] + +static STATIC: [usize; 4] = [0, 1, 8, 16]; +const CONST: [usize; 4] = [0, 1, 8, 16]; +const MAX_LEN: usize = 42; + +fn main() { + let mut vec = vec![1, 2, 3, 4]; + let vec2 = vec![1, 2, 3, 4]; + for i in 0..vec.len() { + println!("{}", vec[i]); + } + + for i in 0..vec.len() { + let i = 42; // make a different `i` + println!("{}", vec[i]); // ok, not the `i` of the for-loop + } + + for i in 0..vec.len() { + let _ = vec[i]; + } + + // ICE #746 + for j in 0..4 { + println!("{:?}", STATIC[j]); + } + + for j in 0..4 { + println!("{:?}", CONST[j]); + } + + for i in 0..vec.len() { + println!("{} {}", vec[i], i); + } + for i in 0..vec.len() { + // not an error, indexing more than one variable + println!("{} {}", vec[i], vec2[i]); + } + + for i in 0..vec.len() { + println!("{}", vec2[i]); + } + + for i in 5..vec.len() { + println!("{}", vec[i]); + } + + for i in 0..MAX_LEN { + println!("{}", vec[i]); + } + + for i in 0..=MAX_LEN { + println!("{}", vec[i]); + } + + for i in 5..10 { + println!("{}", vec[i]); + } + + for i in 5..=10 { + println!("{}", vec[i]); + } + + for i in 5..vec.len() { + println!("{} {}", vec[i], i); + } + + for i in 5..10 { + println!("{} {}", vec[i], i); + } + + // #2542 + for i in 0..vec.len() { + vec[i] = Some(1).unwrap_or_else(|| panic!("error on {}", i)); + } + + // #3788 + let test = Test { + inner: vec![1, 2, 3, 4], + }; + for i in 0..2 { + println!("{}", test[i]); + } +} + +struct Test { + inner: Vec, +} + +impl std::ops::Index for Test { + type Output = usize; + fn index(&self, index: usize) -> &Self::Output { + &self.inner[index] + } +} diff --git a/src/tools/clippy/tests/ui/needless_range_loop.stderr b/src/tools/clippy/tests/ui/needless_range_loop.stderr new file mode 100644 index 0000000000000..c50c4931fb4cc --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop.stderr @@ -0,0 +1,157 @@ +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:10:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | + = note: `-D clippy::needless-range-loop` implied by `-D warnings` +help: consider using an iterator + | +LL | for in &vec { + | ^^^^^^ ^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:19:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in &vec { + | ^^^^^^ ^^^^ + +error: the loop variable `j` is only used to index `STATIC`. + --> $DIR/needless_range_loop.rs:24:14 + | +LL | for j in 0..4 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in &STATIC { + | ^^^^^^ ^^^^^^^ + +error: the loop variable `j` is only used to index `CONST`. + --> $DIR/needless_range_loop.rs:28:14 + | +LL | for j in 0..4 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in &CONST { + | ^^^^^^ ^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:32:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter().enumerate() { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec2`. + --> $DIR/needless_range_loop.rs:40:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec2.iter().take(vec.len()) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:44:14 + | +LL | for i in 5..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().skip(5) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:48:14 + | +LL | for i in 0..MAX_LEN { + | ^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(MAX_LEN) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:52:14 + | +LL | for i in 0..=MAX_LEN { + | ^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(MAX_LEN + 1) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:56:14 + | +LL | for i in 5..10 { + | ^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(10).skip(5) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop.rs:60:14 + | +LL | for i in 5..=10 { + | ^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter().take(10 + 1).skip(5) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:64:14 + | +LL | for i in 5..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter().enumerate().skip(5) { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:68:14 + | +LL | for i in 5..10 { + | ^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter().enumerate().take(10).skip(5) { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is used to index `vec` + --> $DIR/needless_range_loop.rs:73:14 + | +LL | for i in 0..vec.len() { + | ^^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for (i, ) in vec.iter_mut().enumerate() { + | ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_range_loop2.rs b/src/tools/clippy/tests/ui/needless_range_loop2.rs new file mode 100644 index 0000000000000..2ed1b09bece74 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop2.rs @@ -0,0 +1,85 @@ +#![warn(clippy::needless_range_loop)] + +fn calc_idx(i: usize) -> usize { + (i + i + 20) % 4 +} + +fn main() { + let ns = vec![2, 3, 5, 7]; + + for i in 3..10 { + println!("{}", ns[i]); + } + + for i in 3..10 { + println!("{}", ns[i % 4]); + } + + for i in 3..10 { + println!("{}", ns[i % ns.len()]); + } + + for i in 3..10 { + println!("{}", ns[calc_idx(i)]); + } + + for i in 3..10 { + println!("{}", ns[calc_idx(i) % 4]); + } + + let mut ms = vec![1, 2, 3, 4, 5, 6]; + for i in 0..ms.len() { + ms[i] *= 2; + } + assert_eq!(ms, vec![2, 4, 6, 8, 10, 12]); + + let mut ms = vec![1, 2, 3, 4, 5, 6]; + for i in 0..ms.len() { + let x = &mut ms[i]; + *x *= 2; + } + assert_eq!(ms, vec![2, 4, 6, 8, 10, 12]); + + let g = vec![1, 2, 3, 4, 5, 6]; + let glen = g.len(); + for i in 0..glen { + let x: u32 = g[i + 1..].iter().sum(); + println!("{}", g[i] + x); + } + assert_eq!(g, vec![20, 18, 15, 11, 6, 0]); + + let mut g = vec![1, 2, 3, 4, 5, 6]; + let glen = g.len(); + for i in 0..glen { + g[i] = g[i + 1..].iter().sum(); + } + assert_eq!(g, vec![20, 18, 15, 11, 6, 0]); + + let x = 5; + let mut vec = vec![0; 9]; + + for i in x..x + 4 { + vec[i] += 1; + } + + let x = 5; + let mut vec = vec![0; 10]; + + for i in x..=x + 4 { + vec[i] += 1; + } + + let arr = [1, 2, 3]; + + for i in 0..3 { + println!("{}", arr[i]); + } + + for i in 0..2 { + println!("{}", arr[i]); + } + + for i in 1..3 { + println!("{}", arr[i]); + } +} diff --git a/src/tools/clippy/tests/ui/needless_range_loop2.stderr b/src/tools/clippy/tests/ui/needless_range_loop2.stderr new file mode 100644 index 0000000000000..c54ab5ec9809a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_range_loop2.stderr @@ -0,0 +1,91 @@ +error: the loop variable `i` is only used to index `ns`. + --> $DIR/needless_range_loop2.rs:10:14 + | +LL | for i in 3..10 { + | ^^^^^ + | + = note: `-D clippy::needless-range-loop` implied by `-D warnings` +help: consider using an iterator + | +LL | for in ns.iter().take(10).skip(3) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `ms`. + --> $DIR/needless_range_loop2.rs:31:14 + | +LL | for i in 0..ms.len() { + | ^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in &mut ms { + | ^^^^^^ ^^^^^^^ + +error: the loop variable `i` is only used to index `ms`. + --> $DIR/needless_range_loop2.rs:37:14 + | +LL | for i in 0..ms.len() { + | ^^^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in &mut ms { + | ^^^^^^ ^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop2.rs:61:14 + | +LL | for i in x..x + 4 { + | ^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter_mut().skip(x).take(4) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `vec`. + --> $DIR/needless_range_loop2.rs:68:14 + | +LL | for i in x..=x + 4 { + | ^^^^^^^^^ + | +help: consider using an iterator + | +LL | for in vec.iter_mut().skip(x).take(4 + 1) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `arr`. + --> $DIR/needless_range_loop2.rs:74:14 + | +LL | for i in 0..3 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in &arr { + | ^^^^^^ ^^^^ + +error: the loop variable `i` is only used to index `arr`. + --> $DIR/needless_range_loop2.rs:78:14 + | +LL | for i in 0..2 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in arr.iter().take(2) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^ + +error: the loop variable `i` is only used to index `arr`. + --> $DIR/needless_range_loop2.rs:82:14 + | +LL | for i in 1..3 { + | ^^^^ + | +help: consider using an iterator + | +LL | for in arr.iter().skip(1) { + | ^^^^^^ ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_return.fixed b/src/tools/clippy/tests/ui/needless_return.fixed new file mode 100644 index 0000000000000..ad20e2381073a --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return.fixed @@ -0,0 +1,78 @@ +// run-rustfix + +#![allow(unused, clippy::needless_bool)] +#![allow(clippy::if_same_then_else, clippy::single_match)] +#![warn(clippy::needless_return)] + +macro_rules! the_answer { + () => { + 42 + }; +} + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + true +} + +fn test_no_semicolon() -> bool { + true +} + +fn test_if_block() -> bool { + if true { + true + } else { + false + } +} + +fn test_match(x: bool) -> bool { + match x { + true => false, + false => { + true + }, + } +} + +fn test_closure() { + let _ = || { + true + }; + let _ = || true; +} + +fn test_macro_call() -> i32 { + return the_answer!(); +} + +fn test_void_fun() { + +} + +fn test_void_if_fun(b: bool) { + if b { + + } else { + + } +} + +fn test_void_match(x: u32) { + match x { + 0 => (), + _ => {}, + } +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_no_semicolon(); + let _ = test_if_block(); + let _ = test_match(true); + test_closure(); +} diff --git a/src/tools/clippy/tests/ui/needless_return.rs b/src/tools/clippy/tests/ui/needless_return.rs new file mode 100644 index 0000000000000..af0cdfb207ff5 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return.rs @@ -0,0 +1,78 @@ +// run-rustfix + +#![allow(unused, clippy::needless_bool)] +#![allow(clippy::if_same_then_else, clippy::single_match)] +#![warn(clippy::needless_return)] + +macro_rules! the_answer { + () => { + 42 + }; +} + +fn test_end_of_fn() -> bool { + if true { + // no error! + return true; + } + return true; +} + +fn test_no_semicolon() -> bool { + return true; +} + +fn test_if_block() -> bool { + if true { + return true; + } else { + return false; + } +} + +fn test_match(x: bool) -> bool { + match x { + true => return false, + false => { + return true; + }, + } +} + +fn test_closure() { + let _ = || { + return true; + }; + let _ = || return true; +} + +fn test_macro_call() -> i32 { + return the_answer!(); +} + +fn test_void_fun() { + return; +} + +fn test_void_if_fun(b: bool) { + if b { + return; + } else { + return; + } +} + +fn test_void_match(x: u32) { + match x { + 0 => (), + _ => return, + } +} + +fn main() { + let _ = test_end_of_fn(); + let _ = test_no_semicolon(); + let _ = test_if_block(); + let _ = test_match(true); + test_closure(); +} diff --git a/src/tools/clippy/tests/ui/needless_return.stderr b/src/tools/clippy/tests/ui/needless_return.stderr new file mode 100644 index 0000000000000..c34eecbcbb639 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_return.stderr @@ -0,0 +1,76 @@ +error: unneeded `return` statement + --> $DIR/needless_return.rs:18:5 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + | + = note: `-D clippy::needless-return` implied by `-D warnings` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:22:5 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:27:9 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:29:9 + | +LL | return false; + | ^^^^^^^^^^^^^ help: remove `return`: `false` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:35:17 + | +LL | true => return false, + | ^^^^^^^^^^^^ help: remove `return`: `false` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:37:13 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:44:9 + | +LL | return true; + | ^^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:46:16 + | +LL | let _ = || return true; + | ^^^^^^^^^^^ help: remove `return`: `true` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:54:5 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:59:9 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:61:9 + | +LL | return; + | ^^^^^^^ help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:68:14 + | +LL | _ => return, + | ^^^^^^ help: replace `return` with an empty block: `{}` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/needless_update.rs b/src/tools/clippy/tests/ui/needless_update.rs new file mode 100644 index 0000000000000..bfa005a19f910 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_update.rs @@ -0,0 +1,14 @@ +#![warn(clippy::needless_update)] +#![allow(clippy::no_effect)] + +struct S { + pub a: i32, + pub b: i32, +} + +fn main() { + let base = S { a: 0, b: 0 }; + S { ..base }; // no error + S { a: 1, ..base }; // no error + S { a: 1, b: 1, ..base }; +} diff --git a/src/tools/clippy/tests/ui/needless_update.stderr b/src/tools/clippy/tests/ui/needless_update.stderr new file mode 100644 index 0000000000000..133c834880dd9 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_update.stderr @@ -0,0 +1,10 @@ +error: struct update has no effect, all the fields in the struct have already been specified + --> $DIR/needless_update.rs:13:23 + | +LL | S { a: 1, b: 1, ..base }; + | ^^^^ + | + = note: `-D clippy::needless-update` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.rs b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.rs new file mode 100644 index 0000000000000..856a430ba2b55 --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.rs @@ -0,0 +1,61 @@ +//! This test case utilizes `f64` an easy example for `PartialOrd` only types +//! but the lint itself actually validates any expression where the left +//! operand implements `PartialOrd` but not `Ord`. + +use std::cmp::Ordering; + +#[warn(clippy::neg_cmp_op_on_partial_ord)] +fn main() { + let a_value = 1.0; + let another_value = 7.0; + + // --- Bad --- + + // Not Less but potentially Greater, Equal or Uncomparable. + let _not_less = !(a_value < another_value); + + // Not Less or Equal but potentially Greater or Uncomparable. + let _not_less_or_equal = !(a_value <= another_value); + + // Not Greater but potentially Less, Equal or Uncomparable. + let _not_greater = !(a_value > another_value); + + // Not Greater or Equal but potentially Less or Uncomparable. + let _not_greater_or_equal = !(a_value >= another_value); + + // --- Good --- + + let _not_less = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Greater) | Some(Ordering::Equal) => true, + _ => false, + }; + let _not_less_or_equal = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Greater) => true, + _ => false, + }; + let _not_greater = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Less) | Some(Ordering::Equal) => true, + _ => false, + }; + let _not_greater_or_equal = match a_value.partial_cmp(&another_value) { + None | Some(Ordering::Less) => true, + _ => false, + }; + + // --- Should not trigger --- + + let _ = a_value < another_value; + let _ = a_value <= another_value; + let _ = a_value > another_value; + let _ = a_value >= another_value; + + // --- regression tests --- + + // Issue 2856: False positive on assert!() + // + // The macro always negates the result of the given comparison in its + // internal check which automatically triggered the lint. As it's an + // external macro there was no chance to do anything about it which led + // to a whitelisting of all external macros. + assert!(a_value < another_value); +} diff --git a/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.stderr b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.stderr new file mode 100644 index 0000000000000..d05fd34ce33be --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_cmp_op_on_partial_ord.stderr @@ -0,0 +1,28 @@ +error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. + --> $DIR/neg_cmp_op_on_partial_ord.rs:15:21 + | +LL | let _not_less = !(a_value < another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::neg-cmp-op-on-partial-ord` implied by `-D warnings` + +error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. + --> $DIR/neg_cmp_op_on_partial_ord.rs:18:30 + | +LL | let _not_less_or_equal = !(a_value <= another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. + --> $DIR/neg_cmp_op_on_partial_ord.rs:21:24 + | +LL | let _not_greater = !(a_value > another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: The use of negated comparison operators on partially ordered types produces code that is hard to read and refactor. Please consider using the `partial_cmp` method instead, to make it clear that the two values could be incomparable. + --> $DIR/neg_cmp_op_on_partial_ord.rs:24:33 + | +LL | let _not_greater_or_equal = !(a_value >= another_value); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/neg_multiply.rs b/src/tools/clippy/tests/ui/neg_multiply.rs new file mode 100644 index 0000000000000..d4a20ce9db1c8 --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_multiply.rs @@ -0,0 +1,35 @@ +#![warn(clippy::neg_multiply)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +use std::ops::Mul; + +struct X; + +impl Mul for X { + type Output = X; + + fn mul(self, _r: isize) -> Self { + self + } +} + +impl Mul for isize { + type Output = X; + + fn mul(self, _r: X) -> X { + X + } +} + +fn main() { + let x = 0; + + x * -1; + + -1 * x; + + -1 * -1; // should be ok + + X * -1; // should be ok + -1 * X; // should also be ok +} diff --git a/src/tools/clippy/tests/ui/neg_multiply.stderr b/src/tools/clippy/tests/ui/neg_multiply.stderr new file mode 100644 index 0000000000000..f08bbd6a12c59 --- /dev/null +++ b/src/tools/clippy/tests/ui/neg_multiply.stderr @@ -0,0 +1,16 @@ +error: Negation by multiplying with `-1` + --> $DIR/neg_multiply.rs:27:5 + | +LL | x * -1; + | ^^^^^^ + | + = note: `-D clippy::neg-multiply` implied by `-D warnings` + +error: Negation by multiplying with `-1` + --> $DIR/neg_multiply.rs:29:5 + | +LL | -1 * x; + | ^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/never_loop.rs b/src/tools/clippy/tests/ui/never_loop.rs new file mode 100644 index 0000000000000..cbc4ca3916168 --- /dev/null +++ b/src/tools/clippy/tests/ui/never_loop.rs @@ -0,0 +1,204 @@ +#![allow( + clippy::single_match, + unused_assignments, + unused_variables, + clippy::while_immutable_condition +)] + +fn test1() { + let mut x = 0; + loop { + // clippy::never_loop + x += 1; + if x == 1 { + return; + } + break; + } +} + +fn test2() { + let mut x = 0; + loop { + x += 1; + if x == 1 { + break; + } + } +} + +fn test3() { + let mut x = 0; + loop { + // never loops + x += 1; + break; + } +} + +fn test4() { + let mut x = 1; + loop { + x += 1; + match x { + 5 => return, + _ => (), + } + } +} + +fn test5() { + let i = 0; + loop { + // never loops + while i == 0 { + // never loops + break; + } + return; + } +} + +fn test6() { + let mut x = 0; + 'outer: loop { + x += 1; + loop { + // never loops + if x == 5 { + break; + } + continue 'outer; + } + return; + } +} + +fn test7() { + let mut x = 0; + loop { + x += 1; + match x { + 1 => continue, + _ => (), + } + return; + } +} + +fn test8() { + let mut x = 0; + loop { + x += 1; + match x { + 5 => return, + _ => continue, + } + } +} + +fn test9() { + let x = Some(1); + while let Some(y) = x { + // never loops + return; + } +} + +fn test10() { + for x in 0..10 { + // never loops + match x { + 1 => break, + _ => return, + } + } +} + +fn test11 i32>(mut f: F) { + loop { + return match f() { + 1 => continue, + _ => (), + }; + } +} + +pub fn test12(a: bool, b: bool) { + 'label: loop { + loop { + if a { + continue 'label; + } + if b { + break; + } + } + break; + } +} + +pub fn test13() { + let mut a = true; + loop { + // infinite loop + while a { + if true { + a = false; + continue; + } + return; + } + } +} + +pub fn test14() { + let mut a = true; + 'outer: while a { + // never loops + while a { + if a { + a = false; + continue; + } + } + break 'outer; + } +} + +// Issue #1991: the outter loop should not warn. +pub fn test15() { + 'label: loop { + while false { + break 'label; + } + } +} + +// Issue #4058: `continue` in `break` expression +pub fn test16() { + let mut n = 1; + loop { + break if n != 5 { + n += 1; + continue; + }; + } +} + +fn main() { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test8(); + test9(); + test10(); + test11(|| 0); + test12(true, false); + test13(); + test14(); +} diff --git a/src/tools/clippy/tests/ui/never_loop.stderr b/src/tools/clippy/tests/ui/never_loop.stderr new file mode 100644 index 0000000000000..c00b4c78cf28b --- /dev/null +++ b/src/tools/clippy/tests/ui/never_loop.stderr @@ -0,0 +1,100 @@ +error: this loop never actually loops + --> $DIR/never_loop.rs:10:5 + | +LL | / loop { +LL | | // clippy::never_loop +LL | | x += 1; +LL | | if x == 1 { +... | +LL | | break; +LL | | } + | |_____^ + | + = note: `#[deny(clippy::never_loop)]` on by default + +error: this loop never actually loops + --> $DIR/never_loop.rs:32:5 + | +LL | / loop { +LL | | // never loops +LL | | x += 1; +LL | | break; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:52:5 + | +LL | / loop { +LL | | // never loops +LL | | while i == 0 { +LL | | // never loops +... | +LL | | return; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:54:9 + | +LL | / while i == 0 { +LL | | // never loops +LL | | break; +LL | | } + | |_________^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:66:9 + | +LL | / loop { +LL | | // never loops +LL | | if x == 5 { +LL | | break; +LL | | } +LL | | continue 'outer; +LL | | } + | |_________^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:102:5 + | +LL | / while let Some(y) = x { +LL | | // never loops +LL | | return; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:109:5 + | +LL | / for x in 0..10 { +LL | | // never loops +LL | | match x { +LL | | 1 => break, +LL | | _ => return, +LL | | } +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:157:5 + | +LL | / 'outer: while a { +LL | | // never loops +LL | | while a { +LL | | if a { +... | +LL | | break 'outer; +LL | | } + | |_____^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:172:9 + | +LL | / while false { +LL | | break 'label; +LL | | } + | |_________^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/new_ret_no_self.rs b/src/tools/clippy/tests/ui/new_ret_no_self.rs new file mode 100644 index 0000000000000..2c2d1e275893f --- /dev/null +++ b/src/tools/clippy/tests/ui/new_ret_no_self.rs @@ -0,0 +1,212 @@ +#![warn(clippy::new_ret_no_self)] +#![allow(dead_code)] + +fn main() {} + +trait R { + type Item; +} + +trait Q { + type Item; + type Item2; +} + +struct S; + +impl R for S { + type Item = Self; +} + +impl S { + // should not trigger the lint + pub fn new() -> impl R { + S + } +} + +struct S2; + +impl R for S2 { + type Item = Self; +} + +impl S2 { + // should not trigger the lint + pub fn new(_: String) -> impl R { + S2 + } +} + +struct S3; + +impl R for S3 { + type Item = u32; +} + +impl S3 { + // should trigger the lint + pub fn new(_: String) -> impl R { + S3 + } +} + +struct S4; + +impl Q for S4 { + type Item = u32; + type Item2 = Self; +} + +impl S4 { + // should not trigger the lint + pub fn new(_: String) -> impl Q { + S4 + } +} + +struct T; + +impl T { + // should not trigger lint + pub fn new() -> Self { + unimplemented!(); + } +} + +struct U; + +impl U { + // should trigger lint + pub fn new() -> u32 { + unimplemented!(); + } +} + +struct V; + +impl V { + // should trigger lint + pub fn new(_: String) -> u32 { + unimplemented!(); + } +} + +struct TupleReturnerOk; + +impl TupleReturnerOk { + // should not trigger lint + pub fn new() -> (Self, u32) { + unimplemented!(); + } +} + +struct TupleReturnerOk2; + +impl TupleReturnerOk2 { + // should not trigger lint (it doesn't matter which element in the tuple is Self) + pub fn new() -> (u32, Self) { + unimplemented!(); + } +} + +struct TupleReturnerOk3; + +impl TupleReturnerOk3 { + // should not trigger lint (tuple can contain multiple Self) + pub fn new() -> (Self, Self) { + unimplemented!(); + } +} + +struct TupleReturnerBad; + +impl TupleReturnerBad { + // should trigger lint + pub fn new() -> (u32, u32) { + unimplemented!(); + } +} + +struct MutPointerReturnerOk; + +impl MutPointerReturnerOk { + // should not trigger lint + pub fn new() -> *mut Self { + unimplemented!(); + } +} + +struct MutPointerReturnerOk2; + +impl MutPointerReturnerOk2 { + // should not trigger lint + pub fn new() -> *const Self { + unimplemented!(); + } +} + +struct MutPointerReturnerBad; + +impl MutPointerReturnerBad { + // should trigger lint + pub fn new() -> *mut V { + unimplemented!(); + } +} + +struct GenericReturnerOk; + +impl GenericReturnerOk { + // should not trigger lint + pub fn new() -> Option { + unimplemented!(); + } +} + +struct GenericReturnerBad; + +impl GenericReturnerBad { + // should trigger lint + pub fn new() -> Option { + unimplemented!(); + } +} + +struct NestedReturnerOk; + +impl NestedReturnerOk { + // should not trigger lint + pub fn new() -> (Option, u32) { + unimplemented!(); + } +} + +struct NestedReturnerOk2; + +impl NestedReturnerOk2 { + // should not trigger lint + pub fn new() -> ((Self, u32), u32) { + unimplemented!(); + } +} + +struct NestedReturnerOk3; + +impl NestedReturnerOk3 { + // should not trigger lint + pub fn new() -> Option<(Self, u32)> { + unimplemented!(); + } +} + +struct WithLifetime<'a> { + cat: &'a str, +} + +impl<'a> WithLifetime<'a> { + // should not trigger the lint, because the lifetimes are different + pub fn new<'b: 'a>(s: &'b str) -> WithLifetime<'b> { + unimplemented!(); + } +} diff --git a/src/tools/clippy/tests/ui/new_ret_no_self.stderr b/src/tools/clippy/tests/ui/new_ret_no_self.stderr new file mode 100644 index 0000000000000..dd5a24bcbe7ae --- /dev/null +++ b/src/tools/clippy/tests/ui/new_ret_no_self.stderr @@ -0,0 +1,52 @@ +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:49:5 + | +LL | / pub fn new(_: String) -> impl R { +LL | | S3 +LL | | } + | |_____^ + | + = note: `-D clippy::new-ret-no-self` implied by `-D warnings` + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:81:5 + | +LL | / pub fn new() -> u32 { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:90:5 + | +LL | / pub fn new(_: String) -> u32 { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:126:5 + | +LL | / pub fn new() -> (u32, u32) { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:153:5 + | +LL | / pub fn new() -> *mut V { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: methods called `new` usually return `Self` + --> $DIR/new_ret_no_self.rs:171:5 + | +LL | / pub fn new() -> Option { +LL | | unimplemented!(); +LL | | } + | |_____^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/new_without_default.rs b/src/tools/clippy/tests/ui/new_without_default.rs new file mode 100644 index 0000000000000..781ea7bb15283 --- /dev/null +++ b/src/tools/clippy/tests/ui/new_without_default.rs @@ -0,0 +1,151 @@ +#![feature(const_fn)] +#![allow(dead_code, clippy::missing_safety_doc)] +#![warn(clippy::new_without_default)] + +pub struct Foo; + +impl Foo { + pub fn new() -> Foo { + Foo + } +} + +pub struct Bar; + +impl Bar { + pub fn new() -> Self { + Bar + } +} + +pub struct Ok; + +impl Ok { + pub fn new() -> Self { + Ok + } +} + +impl Default for Ok { + fn default() -> Self { + Ok + } +} + +pub struct Params; + +impl Params { + pub fn new(_: u32) -> Self { + Params + } +} + +pub struct GenericsOk { + bar: T, +} + +impl Default for GenericsOk { + fn default() -> Self { + unimplemented!(); + } +} + +impl<'c, V> GenericsOk { + pub fn new() -> GenericsOk { + unimplemented!() + } +} + +pub struct LtOk<'a> { + foo: &'a bool, +} + +impl<'b> Default for LtOk<'b> { + fn default() -> Self { + unimplemented!(); + } +} + +impl<'c> LtOk<'c> { + pub fn new() -> LtOk<'c> { + unimplemented!() + } +} + +pub struct LtKo<'a> { + foo: &'a bool, +} + +impl<'c> LtKo<'c> { + pub fn new() -> LtKo<'c> { + unimplemented!() + } + // FIXME: that suggestion is missing lifetimes +} + +struct Private; + +impl Private { + fn new() -> Private { + unimplemented!() + } // We don't lint private items +} + +struct Const; + +impl Const { + pub const fn new() -> Const { + Const + } // const fns can't be implemented via Default +} + +pub struct IgnoreGenericNew; + +impl IgnoreGenericNew { + pub fn new() -> Self { + IgnoreGenericNew + } // the derived Default does not make sense here as the result depends on T +} + +pub trait TraitWithNew: Sized { + fn new() -> Self { + panic!() + } +} + +pub struct IgnoreUnsafeNew; + +impl IgnoreUnsafeNew { + pub unsafe fn new() -> Self { + IgnoreUnsafeNew + } +} + +#[derive(Default)] +pub struct OptionRefWrapper<'a, T>(Option<&'a T>); + +impl<'a, T> OptionRefWrapper<'a, T> { + pub fn new() -> Self { + OptionRefWrapper(None) + } +} + +pub struct Allow(Foo); + +impl Allow { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + unimplemented!() + } +} + +pub struct AllowDerive; + +impl AllowDerive { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + unimplemented!() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/new_without_default.stderr b/src/tools/clippy/tests/ui/new_without_default.stderr new file mode 100644 index 0000000000000..5e485d40663f3 --- /dev/null +++ b/src/tools/clippy/tests/ui/new_without_default.stderr @@ -0,0 +1,46 @@ +error: you should consider deriving a `Default` implementation for `Foo` + --> $DIR/new_without_default.rs:8:5 + | +LL | / pub fn new() -> Foo { +LL | | Foo +LL | | } + | |_____^ + | + = note: `-D clippy::new-without-default` implied by `-D warnings` +help: try this + | +LL | #[derive(Default)] + | + +error: you should consider deriving a `Default` implementation for `Bar` + --> $DIR/new_without_default.rs:16:5 + | +LL | / pub fn new() -> Self { +LL | | Bar +LL | | } + | |_____^ + | +help: try this + | +LL | #[derive(Default)] + | + +error: you should consider adding a `Default` implementation for `LtKo<'c>` + --> $DIR/new_without_default.rs:80:5 + | +LL | / pub fn new() -> LtKo<'c> { +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try this + | +LL | impl Default for LtKo<'c> { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } + | + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/no_effect.rs b/src/tools/clippy/tests/ui/no_effect.rs new file mode 100644 index 0000000000000..8fbfcb79860a5 --- /dev/null +++ b/src/tools/clippy/tests/ui/no_effect.rs @@ -0,0 +1,102 @@ +#![feature(box_syntax)] +#![warn(clippy::no_effect)] +#![allow(dead_code)] +#![allow(path_statements)] +#![allow(clippy::deref_addrof)] +#![allow(clippy::redundant_field_names)] +#![feature(untagged_unions)] + +struct Unit; +struct Tuple(i32); +struct Struct { + field: i32, +} +enum Enum { + Tuple(i32), + Struct { field: i32 }, +} +struct DropUnit; +impl Drop for DropUnit { + fn drop(&mut self) {} +} +struct DropStruct { + field: i32, +} +impl Drop for DropStruct { + fn drop(&mut self) {} +} +struct DropTuple(i32); +impl Drop for DropTuple { + fn drop(&mut self) {} +} +enum DropEnum { + Tuple(i32), + Struct { field: i32 }, +} +impl Drop for DropEnum { + fn drop(&mut self) {} +} +struct FooString { + s: String, +} +union Union { + a: u8, + b: f64, +} + +fn get_number() -> i32 { + 0 +} +fn get_struct() -> Struct { + Struct { field: 0 } +} +fn get_drop_struct() -> DropStruct { + DropStruct { field: 0 } +} + +unsafe fn unsafe_fn() -> i32 { + 0 +} + +fn main() { + let s = get_struct(); + let s2 = get_struct(); + + 0; + s2; + Unit; + Tuple(0); + Struct { field: 0 }; + Struct { ..s }; + Union { a: 0 }; + Enum::Tuple(0); + Enum::Struct { field: 0 }; + 5 + 6; + *&42; + &6; + (5, 6, 7); + box 42; + ..; + 5..; + ..5; + 5..6; + 5..=6; + [42, 55]; + [42, 55][1]; + (42, 55).1; + [42; 55]; + [42; 55][13]; + let mut x = 0; + || x += 5; + let s: String = "foo".into(); + FooString { s: s }; + + // Do not warn + get_number(); + unsafe { unsafe_fn() }; + DropUnit; + DropStruct { field: 0 }; + DropTuple(0); + DropEnum::Tuple(0); + DropEnum::Struct { field: 0 }; +} diff --git a/src/tools/clippy/tests/ui/no_effect.stderr b/src/tools/clippy/tests/ui/no_effect.stderr new file mode 100644 index 0000000000000..834b9056e311d --- /dev/null +++ b/src/tools/clippy/tests/ui/no_effect.stderr @@ -0,0 +1,154 @@ +error: statement with no effect + --> $DIR/no_effect.rs:65:5 + | +LL | 0; + | ^^ + | + = note: `-D clippy::no-effect` implied by `-D warnings` + +error: statement with no effect + --> $DIR/no_effect.rs:66:5 + | +LL | s2; + | ^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:67:5 + | +LL | Unit; + | ^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:68:5 + | +LL | Tuple(0); + | ^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:69:5 + | +LL | Struct { field: 0 }; + | ^^^^^^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:70:5 + | +LL | Struct { ..s }; + | ^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:71:5 + | +LL | Union { a: 0 }; + | ^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:72:5 + | +LL | Enum::Tuple(0); + | ^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:73:5 + | +LL | Enum::Struct { field: 0 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:74:5 + | +LL | 5 + 6; + | ^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:75:5 + | +LL | *&42; + | ^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:76:5 + | +LL | &6; + | ^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:77:5 + | +LL | (5, 6, 7); + | ^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:78:5 + | +LL | box 42; + | ^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:79:5 + | +LL | ..; + | ^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:80:5 + | +LL | 5..; + | ^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:81:5 + | +LL | ..5; + | ^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:82:5 + | +LL | 5..6; + | ^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:84:5 + | +LL | [42, 55]; + | ^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:85:5 + | +LL | [42, 55][1]; + | ^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:86:5 + | +LL | (42, 55).1; + | ^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:87:5 + | +LL | [42; 55]; + | ^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:88:5 + | +LL | [42; 55][13]; + | ^^^^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:90:5 + | +LL | || x += 5; + | ^^^^^^^^^^ + +error: statement with no effect + --> $DIR/no_effect.rs:92:5 + | +LL | FooString { s: s }; + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 25 previous errors + diff --git a/src/tools/clippy/tests/ui/non_expressive_names.rs b/src/tools/clippy/tests/ui/non_expressive_names.rs new file mode 100644 index 0000000000000..58415b4aede26 --- /dev/null +++ b/src/tools/clippy/tests/ui/non_expressive_names.rs @@ -0,0 +1,56 @@ +#![warn(clippy::all)] +#![allow(unused, clippy::println_empty_string)] + +#[derive(Clone, Debug)] +enum MaybeInst { + Split, + Split1(usize), + Split2(usize), +} + +struct InstSplit { + uiae: usize, +} + +impl MaybeInst { + fn fill(&mut self) { + let filled = match *self { + MaybeInst::Split1(goto1) => panic!(1), + MaybeInst::Split2(goto2) => panic!(2), + _ => unimplemented!(), + }; + unimplemented!() + } +} + +fn underscores_and_numbers() { + let _1 = 1; //~ERROR Consider a more descriptive name + let ____1 = 1; //~ERROR Consider a more descriptive name + let __1___2 = 12; //~ERROR Consider a more descriptive name + let _1_ok = 1; +} + +fn issue2927() { + let args = 1; + format!("{:?}", 2); +} + +fn issue3078() { + match "a" { + stringify!(a) => {}, + _ => {}, + } +} + +struct Bar; + +impl Bar { + fn bar() { + let _1 = 1; + let ____1 = 1; + let __1___2 = 12; + let _1_ok = 1; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/non_expressive_names.stderr b/src/tools/clippy/tests/ui/non_expressive_names.stderr new file mode 100644 index 0000000000000..a0ca46f0efc60 --- /dev/null +++ b/src/tools/clippy/tests/ui/non_expressive_names.stderr @@ -0,0 +1,40 @@ +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:27:9 + | +LL | let _1 = 1; //~ERROR Consider a more descriptive name + | ^^ + | + = note: `-D clippy::just-underscores-and-digits` implied by `-D warnings` + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:28:9 + | +LL | let ____1 = 1; //~ERROR Consider a more descriptive name + | ^^^^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:29:9 + | +LL | let __1___2 = 12; //~ERROR Consider a more descriptive name + | ^^^^^^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:49:13 + | +LL | let _1 = 1; + | ^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:50:13 + | +LL | let ____1 = 1; + | ^^^^^ + +error: consider choosing a more descriptive name + --> $DIR/non_expressive_names.rs:51:13 + | +LL | let __1___2 = 12; + | ^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/non_expressive_names.stdout b/src/tools/clippy/tests/ui/non_expressive_names.stdout new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.rs b/src/tools/clippy/tests/ui/nonminimal_bool.rs new file mode 100644 index 0000000000000..7ea154cb9b018 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool.rs @@ -0,0 +1,52 @@ +#![allow(unused, clippy::many_single_char_names)] +#![warn(clippy::nonminimal_bool)] + +fn main() { + let a: bool = unimplemented!(); + let b: bool = unimplemented!(); + let c: bool = unimplemented!(); + let d: bool = unimplemented!(); + let e: bool = unimplemented!(); + let _ = !true; + let _ = !false; + let _ = !!a; + let _ = false || a; + // don't lint on cfgs + let _ = cfg!(you_shall_not_not_pass) && a; + let _ = a || !b || !c || !d || !e; + let _ = !(!a && b); + let _ = !(!a || b); + let _ = !a && !(b && c); +} + +fn equality_stuff() { + let a: i32 = unimplemented!(); + let b: i32 = unimplemented!(); + let c: i32 = unimplemented!(); + let d: i32 = unimplemented!(); + let _ = a == b && c == 5 && a == b; + let _ = a == b || c == 5 || a == b; + let _ = a == b && c == 5 && b == a; + let _ = a != b || !(a != b || c == d); + let _ = a != b && !(a != b && c == d); +} + +fn issue3847(a: u32, b: u32) -> bool { + const THRESHOLD: u32 = 1_000; + + if a < THRESHOLD && b >= THRESHOLD || a >= THRESHOLD && b < THRESHOLD { + return false; + } + true +} + +fn issue4548() { + fn f(_i: u32, _j: u32) -> u32 { + unimplemented!(); + } + + let i = 0; + let j = 0; + + if i != j && f(i, j) != 0 || i == j && f(i, j) != 1 {} +} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.stderr b/src/tools/clippy/tests/ui/nonminimal_bool.stderr new file mode 100644 index 0000000000000..d34d106cb2fbb --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool.stderr @@ -0,0 +1,111 @@ +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:10:13 + | +LL | let _ = !true; + | ^^^^^ help: try: `false` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:11:13 + | +LL | let _ = !false; + | ^^^^^^ help: try: `true` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:12:13 + | +LL | let _ = !!a; + | ^^^ help: try: `a` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:13:13 + | +LL | let _ = false || a; + | ^^^^^^^^^^ help: try: `a` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:17:13 + | +LL | let _ = !(!a && b); + | ^^^^^^^^^^ help: try: `a || !b` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:18:13 + | +LL | let _ = !(!a || b); + | ^^^^^^^^^^ help: try: `a && !b` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:19:13 + | +LL | let _ = !a && !(b && c); + | ^^^^^^^^^^^^^^^ help: try: `!(a || b && c)` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:27:13 + | +LL | let _ = a == b && c == 5 && a == b; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a == b && c == 5; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a != b || c != 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:28:13 + | +LL | let _ = a == b || c == 5 || a == b; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a == b || c == 5; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a != b && c != 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:29:13 + | +LL | let _ = a == b && c == 5 && b == a; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a == b && c == 5; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a != b || c != 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:30:13 + | +LL | let _ = a != b || !(a != b || c == d); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a != b || c != d; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a == b && c == d); + | ^^^^^^^^^^^^^^^^^^^ + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool.rs:31:13 + | +LL | let _ = a != b && !(a != b && c == d); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | let _ = a != b && c != d; + | ^^^^^^^^^^^^^^^^ +LL | let _ = !(a == b || c == d); + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs b/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs new file mode 100644 index 0000000000000..4de48cd0879a1 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.rs @@ -0,0 +1,110 @@ +#![allow(unused, clippy::many_single_char_names)] +#![warn(clippy::nonminimal_bool)] + +fn methods_with_negation() { + let a: Option = unimplemented!(); + let b: Result = unimplemented!(); + let _ = a.is_some(); + let _ = !a.is_some(); + let _ = a.is_none(); + let _ = !a.is_none(); + let _ = b.is_err(); + let _ = !b.is_err(); + let _ = b.is_ok(); + let _ = !b.is_ok(); + let c = false; + let _ = !(a.is_some() && !c); + let _ = !(a.is_some() || !c); + let _ = !(!c ^ c) || !a.is_some(); + let _ = (!c ^ c) || !a.is_some(); + let _ = !c ^ c || !a.is_some(); +} + +// Simplified versions of https://github.com/rust-lang/rust-clippy/issues/2638 +// clippy::nonminimal_bool should only check the built-in Result and Some type, not +// any other types like the following. +enum CustomResultOk { + Ok, + Err(E), +} +enum CustomResultErr { + Ok, + Err(E), +} +enum CustomSomeSome { + Some(T), + None, +} +enum CustomSomeNone { + Some(T), + None, +} + +impl CustomResultOk { + pub fn is_ok(&self) -> bool { + true + } +} + +impl CustomResultErr { + pub fn is_err(&self) -> bool { + true + } +} + +impl CustomSomeSome { + pub fn is_some(&self) -> bool { + true + } +} + +impl CustomSomeNone { + pub fn is_none(&self) -> bool { + true + } +} + +fn dont_warn_for_custom_methods_with_negation() { + let res = CustomResultOk::Err("Error"); + // Should not warn and suggest 'is_err()' because the type does not + // implement is_err(). + if !res.is_ok() {} + + let res = CustomResultErr::Err("Error"); + // Should not warn and suggest 'is_ok()' because the type does not + // implement is_ok(). + if !res.is_err() {} + + let res = CustomSomeSome::Some("thing"); + // Should not warn and suggest 'is_none()' because the type does not + // implement is_none(). + if !res.is_some() {} + + let res = CustomSomeNone::Some("thing"); + // Should not warn and suggest 'is_some()' because the type does not + // implement is_some(). + if !res.is_none() {} +} + +// Only Built-in Result and Some types should suggest the negated alternative +fn warn_for_built_in_methods_with_negation() { + let res: Result = Ok(1); + if !res.is_ok() {} + if !res.is_err() {} + + let res = Some(1); + if !res.is_some() {} + if !res.is_none() {} +} + +#[allow(clippy::neg_cmp_op_on_partial_ord)] +fn dont_warn_for_negated_partial_ord_comparison() { + let a: f64 = unimplemented!(); + let b: f64 = unimplemented!(); + let _ = !(a < b); + let _ = !(a <= b); + let _ = !(a > b); + let _ = !(a >= b); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr b/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr new file mode 100644 index 0000000000000..a2df889d62302 --- /dev/null +++ b/src/tools/clippy/tests/ui/nonminimal_bool_methods.stderr @@ -0,0 +1,82 @@ +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:8:13 + | +LL | let _ = !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + | + = note: `-D clippy::nonminimal-bool` implied by `-D warnings` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:10:13 + | +LL | let _ = !a.is_none(); + | ^^^^^^^^^^^^ help: try: `a.is_some()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:12:13 + | +LL | let _ = !b.is_err(); + | ^^^^^^^^^^^ help: try: `b.is_ok()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:14:13 + | +LL | let _ = !b.is_ok(); + | ^^^^^^^^^^ help: try: `b.is_err()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:16:13 + | +LL | let _ = !(a.is_some() && !c); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `a.is_none() || c` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:17:13 + | +LL | let _ = !(a.is_some() || !c); + | ^^^^^^^^^^^^^^^^^^^^ help: try: `a.is_none() && c` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:18:26 + | +LL | let _ = !(!c ^ c) || !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:19:25 + | +LL | let _ = (!c ^ c) || !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:20:23 + | +LL | let _ = !c ^ c || !a.is_some(); + | ^^^^^^^^^^^^ help: try: `a.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:92:8 + | +LL | if !res.is_ok() {} + | ^^^^^^^^^^^^ help: try: `res.is_err()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:93:8 + | +LL | if !res.is_err() {} + | ^^^^^^^^^^^^^ help: try: `res.is_ok()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:96:8 + | +LL | if !res.is_some() {} + | ^^^^^^^^^^^^^^ help: try: `res.is_none()` + +error: this boolean expression can be simplified + --> $DIR/nonminimal_bool_methods.rs:97:8 + | +LL | if !res.is_none() {} + | ^^^^^^^^^^^^^^ help: try: `res.is_some()` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/ok_expect.rs b/src/tools/clippy/tests/ui/ok_expect.rs new file mode 100644 index 0000000000000..ff68d38c73bf5 --- /dev/null +++ b/src/tools/clippy/tests/ui/ok_expect.rs @@ -0,0 +1,27 @@ +use std::io; + +struct MyError(()); // doesn't implement Debug + +#[derive(Debug)] +struct MyErrorWithParam { + x: T, +} + +fn main() { + let res: Result = Ok(0); + let _ = res.unwrap(); + + res.ok().expect("disaster!"); + // the following should not warn, since `expect` isn't implemented unless + // the error type implements `Debug` + let res2: Result = Ok(0); + res2.ok().expect("oh noes!"); + let res3: Result> = Ok(0); + res3.ok().expect("whoof"); + let res4: Result = Ok(0); + res4.ok().expect("argh"); + let res5: io::Result = Ok(0); + res5.ok().expect("oops"); + let res6: Result = Ok(0); + res6.ok().expect("meh"); +} diff --git a/src/tools/clippy/tests/ui/ok_expect.stderr b/src/tools/clippy/tests/ui/ok_expect.stderr new file mode 100644 index 0000000000000..b02b28e7f68c8 --- /dev/null +++ b/src/tools/clippy/tests/ui/ok_expect.stderr @@ -0,0 +1,43 @@ +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:14:5 + | +LL | res.ok().expect("disaster!"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::ok-expect` implied by `-D warnings` + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:20:5 + | +LL | res3.ok().expect("whoof"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:22:5 + | +LL | res4.ok().expect("argh"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:24:5 + | +LL | res5.ok().expect("oops"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: called `ok().expect()` on a `Result` value + --> $DIR/ok_expect.rs:26:5 + | +LL | res6.ok().expect("meh"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: you can call `expect()` directly on the `Result` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/op_ref.rs b/src/tools/clippy/tests/ui/op_ref.rs new file mode 100644 index 0000000000000..6605c967c8e7e --- /dev/null +++ b/src/tools/clippy/tests/ui/op_ref.rs @@ -0,0 +1,58 @@ +#![allow(unused_variables, clippy::blacklisted_name)] +#![warn(clippy::op_ref)] +#![allow(clippy::many_single_char_names)] +use std::collections::HashSet; +use std::ops::BitAnd; + +fn main() { + let tracked_fds: HashSet = HashSet::new(); + let new_fds = HashSet::new(); + let unwanted = &tracked_fds - &new_fds; + + let foo = &5 - &6; + + let bar = String::new(); + let bar = "foo" == &bar; + + let a = "a".to_string(); + let b = "a"; + + if b < &a { + println!("OK"); + } + + struct X(i32); + impl BitAnd for X { + type Output = X; + fn bitand(self, rhs: X) -> X { + X(self.0 & rhs.0) + } + } + impl<'a> BitAnd<&'a X> for X { + type Output = X; + fn bitand(self, rhs: &'a X) -> X { + X(self.0 & rhs.0) + } + } + let x = X(1); + let y = X(2); + let z = x & &y; + + #[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 & &y; +} diff --git a/src/tools/clippy/tests/ui/op_ref.stderr b/src/tools/clippy/tests/ui/op_ref.stderr new file mode 100644 index 0000000000000..a3a9adcc48355 --- /dev/null +++ b/src/tools/clippy/tests/ui/op_ref.stderr @@ -0,0 +1,22 @@ +error: needlessly taken reference of both operands + --> $DIR/op_ref.rs:12:15 + | +LL | let foo = &5 - &6; + | ^^^^^^^ + | + = note: `-D clippy::op-ref` implied by `-D warnings` +help: use the values directly + | +LL | let foo = 5 - 6; + | ^ ^ + +error: taken reference of right operand + --> $DIR/op_ref.rs:57:13 + | +LL | let z = x & &y; + | ^^^^-- + | | + | help: use the right value directly: `y` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/open_options.rs b/src/tools/clippy/tests/ui/open_options.rs new file mode 100644 index 0000000000000..9063fafbcd040 --- /dev/null +++ b/src/tools/clippy/tests/ui/open_options.rs @@ -0,0 +1,14 @@ +use std::fs::OpenOptions; + +#[allow(unused_must_use)] +#[warn(clippy::nonsensical_open_options)] +fn main() { + OpenOptions::new().read(true).truncate(true).open("foo.txt"); + OpenOptions::new().append(true).truncate(true).open("foo.txt"); + + OpenOptions::new().read(true).read(false).open("foo.txt"); + OpenOptions::new().create(true).create(false).open("foo.txt"); + OpenOptions::new().write(true).write(false).open("foo.txt"); + OpenOptions::new().append(true).append(false).open("foo.txt"); + OpenOptions::new().truncate(true).truncate(false).open("foo.txt"); +} diff --git a/src/tools/clippy/tests/ui/open_options.stderr b/src/tools/clippy/tests/ui/open_options.stderr new file mode 100644 index 0000000000000..26fe9f6fb206f --- /dev/null +++ b/src/tools/clippy/tests/ui/open_options.stderr @@ -0,0 +1,46 @@ +error: file opened with `truncate` and `read` + --> $DIR/open_options.rs:6:5 + | +LL | OpenOptions::new().read(true).truncate(true).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::nonsensical-open-options` implied by `-D warnings` + +error: file opened with `append` and `truncate` + --> $DIR/open_options.rs:7:5 + | +LL | OpenOptions::new().append(true).truncate(true).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `read` is called more than once + --> $DIR/open_options.rs:9:5 + | +LL | OpenOptions::new().read(true).read(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `create` is called more than once + --> $DIR/open_options.rs:10:5 + | +LL | OpenOptions::new().create(true).create(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `write` is called more than once + --> $DIR/open_options.rs:11:5 + | +LL | OpenOptions::new().write(true).write(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `append` is called more than once + --> $DIR/open_options.rs:12:5 + | +LL | OpenOptions::new().append(true).append(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the method `truncate` is called more than once + --> $DIR/open_options.rs:13:5 + | +LL | OpenOptions::new().truncate(true).truncate(false).open("foo.txt"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/option_and_then_some.fixed b/src/tools/clippy/tests/ui/option_and_then_some.fixed new file mode 100644 index 0000000000000..035bc1e54656b --- /dev/null +++ b/src/tools/clippy/tests/ui/option_and_then_some.fixed @@ -0,0 +1,25 @@ +// run-rustfix +#![deny(clippy::option_and_then_some)] + +// need a main anyway, use it get rid of unused warnings too +pub fn main() { + let x = Some(5); + // the easiest cases + let _ = x; + let _ = x.map(|o| o + 1); + // and an easy counter-example + let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); + + // Different type + let x: Result = Ok(1); + let _ = x.and_then(Ok); +} + +pub fn foo() -> Option { + let x = Some(String::from("hello")); + Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) +} + +pub fn example2(x: bool) -> Option<&'static str> { + Some("a").and_then(|s| Some(if x { s } else { return None })) +} diff --git a/src/tools/clippy/tests/ui/option_and_then_some.rs b/src/tools/clippy/tests/ui/option_and_then_some.rs new file mode 100644 index 0000000000000..d49da7813c6e6 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_and_then_some.rs @@ -0,0 +1,25 @@ +// run-rustfix +#![deny(clippy::option_and_then_some)] + +// need a main anyway, use it get rid of unused warnings too +pub fn main() { + let x = Some(5); + // the easiest cases + let _ = x.and_then(Some); + let _ = x.and_then(|o| Some(o + 1)); + // and an easy counter-example + let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); + + // Different type + let x: Result = Ok(1); + let _ = x.and_then(Ok); +} + +pub fn foo() -> Option { + let x = Some(String::from("hello")); + Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?))) +} + +pub fn example2(x: bool) -> Option<&'static str> { + Some("a").and_then(|s| Some(if x { s } else { return None })) +} diff --git a/src/tools/clippy/tests/ui/option_and_then_some.stderr b/src/tools/clippy/tests/ui/option_and_then_some.stderr new file mode 100644 index 0000000000000..4782549176566 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_and_then_some.stderr @@ -0,0 +1,20 @@ +error: using `Option.and_then(Some)`, which is a no-op + --> $DIR/option_and_then_some.rs:8:13 + | +LL | let _ = x.and_then(Some); + | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` + | +note: the lint level is defined here + --> $DIR/option_and_then_some.rs:2:9 + | +LL | #![deny(clippy::option_and_then_some)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` + --> $DIR/option_and_then_some.rs:9:13 + | +LL | let _ = x.and_then(|o| Some(o + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/option_as_ref_deref.fixed b/src/tools/clippy/tests/ui/option_as_ref_deref.fixed new file mode 100644 index 0000000000000..076692e644517 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_as_ref_deref.fixed @@ -0,0 +1,41 @@ +// run-rustfix + +#![allow(unused_imports, clippy::redundant_clone)] +#![warn(clippy::option_as_ref_deref)] + +use std::ffi::{CString, OsString}; +use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; + +fn main() { + let mut opt = Some(String::from("123")); + + let _ = opt.clone().as_deref().map(str::len); + + #[rustfmt::skip] + let _ = opt.clone().as_deref() + .map(str::len); + + let _ = opt.as_deref_mut(); + + let _ = opt.as_deref(); + let _ = opt.as_deref(); + let _ = opt.as_deref_mut(); + let _ = opt.as_deref_mut(); + let _ = Some(CString::new(vec![]).unwrap()).as_deref(); + let _ = Some(OsString::new()).as_deref(); + let _ = Some(PathBuf::new()).as_deref(); + let _ = Some(Vec::<()>::new()).as_deref(); + let _ = Some(Vec::<()>::new()).as_deref_mut(); + + let _ = opt.as_deref(); + let _ = opt.clone().as_deref_mut().map(|x| x.len()); + + let vc = vec![String::new()]; + let _ = Some(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted + + let _: Option<&str> = Some(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted + + let _ = opt.as_deref(); + let _ = opt.as_deref_mut(); +} diff --git a/src/tools/clippy/tests/ui/option_as_ref_deref.rs b/src/tools/clippy/tests/ui/option_as_ref_deref.rs new file mode 100644 index 0000000000000..3bf5f715f8339 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_as_ref_deref.rs @@ -0,0 +1,44 @@ +// run-rustfix + +#![allow(unused_imports, clippy::redundant_clone)] +#![warn(clippy::option_as_ref_deref)] + +use std::ffi::{CString, OsString}; +use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; + +fn main() { + let mut opt = Some(String::from("123")); + + let _ = opt.clone().as_ref().map(Deref::deref).map(str::len); + + #[rustfmt::skip] + let _ = opt.clone() + .as_ref().map( + Deref::deref + ) + .map(str::len); + + let _ = opt.as_mut().map(DerefMut::deref_mut); + + let _ = opt.as_ref().map(String::as_str); + let _ = opt.as_ref().map(|x| x.as_str()); + let _ = opt.as_mut().map(String::as_mut_str); + let _ = opt.as_mut().map(|x| x.as_mut_str()); + let _ = Some(CString::new(vec![]).unwrap()).as_ref().map(CString::as_c_str); + let _ = Some(OsString::new()).as_ref().map(OsString::as_os_str); + let _ = Some(PathBuf::new()).as_ref().map(PathBuf::as_path); + let _ = Some(Vec::<()>::new()).as_ref().map(Vec::as_slice); + let _ = Some(Vec::<()>::new()).as_mut().map(Vec::as_mut_slice); + + let _ = opt.as_ref().map(|x| x.deref()); + let _ = opt.clone().as_mut().map(|x| x.deref_mut()).map(|x| x.len()); + + let vc = vec![String::new()]; + let _ = Some(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted + + let _: Option<&str> = Some(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted + + let _ = opt.as_ref().map(|x| &**x); + let _ = opt.as_mut().map(|x| &mut **x); +} diff --git a/src/tools/clippy/tests/ui/option_as_ref_deref.stderr b/src/tools/clippy/tests/ui/option_as_ref_deref.stderr new file mode 100644 index 0000000000000..a106582a63323 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_as_ref_deref.stderr @@ -0,0 +1,104 @@ +error: called `.as_ref().map(Deref::deref)` on an Option value. This can be done more directly by calling `opt.clone().as_deref()` instead + --> $DIR/option_as_ref_deref.rs:13:13 + | +LL | let _ = opt.clone().as_ref().map(Deref::deref).map(str::len); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.clone().as_deref()` + | + = note: `-D clippy::option-as-ref-deref` implied by `-D warnings` + +error: called `.as_ref().map(Deref::deref)` on an Option value. This can be done more directly by calling `opt.clone().as_deref()` instead + --> $DIR/option_as_ref_deref.rs:16:13 + | +LL | let _ = opt.clone() + | _____________^ +LL | | .as_ref().map( +LL | | Deref::deref +LL | | ) + | |_________^ help: try using as_deref instead: `opt.clone().as_deref()` + +error: called `.as_mut().map(DerefMut::deref_mut)` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:22:13 + | +LL | let _ = opt.as_mut().map(DerefMut::deref_mut); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_ref().map(String::as_str)` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:24:13 + | +LL | let _ = opt.as_ref().map(String::as_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_ref().map(|x| x.as_str())` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:25:13 + | +LL | let _ = opt.as_ref().map(|x| x.as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_mut().map(String::as_mut_str)` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:26:13 + | +LL | let _ = opt.as_mut().map(String::as_mut_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_mut().map(|x| x.as_mut_str())` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:27:13 + | +LL | let _ = opt.as_mut().map(|x| x.as_mut_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: called `.as_ref().map(CString::as_c_str)` on an Option value. This can be done more directly by calling `Some(CString::new(vec![]).unwrap()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:28:13 + | +LL | let _ = Some(CString::new(vec![]).unwrap()).as_ref().map(CString::as_c_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(CString::new(vec![]).unwrap()).as_deref()` + +error: called `.as_ref().map(OsString::as_os_str)` on an Option value. This can be done more directly by calling `Some(OsString::new()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:29:13 + | +LL | let _ = Some(OsString::new()).as_ref().map(OsString::as_os_str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(OsString::new()).as_deref()` + +error: called `.as_ref().map(PathBuf::as_path)` on an Option value. This can be done more directly by calling `Some(PathBuf::new()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:30:13 + | +LL | let _ = Some(PathBuf::new()).as_ref().map(PathBuf::as_path); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(PathBuf::new()).as_deref()` + +error: called `.as_ref().map(Vec::as_slice)` on an Option value. This can be done more directly by calling `Some(Vec::<()>::new()).as_deref()` instead + --> $DIR/option_as_ref_deref.rs:31:13 + | +LL | let _ = Some(Vec::<()>::new()).as_ref().map(Vec::as_slice); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `Some(Vec::<()>::new()).as_deref()` + +error: called `.as_mut().map(Vec::as_mut_slice)` on an Option value. This can be done more directly by calling `Some(Vec::<()>::new()).as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:32:13 + | +LL | let _ = Some(Vec::<()>::new()).as_mut().map(Vec::as_mut_slice); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `Some(Vec::<()>::new()).as_deref_mut()` + +error: called `.as_ref().map(|x| x.deref())` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:34:13 + | +LL | let _ = opt.as_ref().map(|x| x.deref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_mut().map(|x| x.deref_mut())` on an Option value. This can be done more directly by calling `opt.clone().as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:35:13 + | +LL | let _ = opt.clone().as_mut().map(|x| x.deref_mut()).map(|x| x.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.clone().as_deref_mut()` + +error: called `.as_ref().map(|x| &**x)` on an Option value. This can be done more directly by calling `opt.as_deref()` instead + --> $DIR/option_as_ref_deref.rs:42:13 + | +LL | let _ = opt.as_ref().map(|x| &**x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref instead: `opt.as_deref()` + +error: called `.as_mut().map(|x| &mut **x)` on an Option value. This can be done more directly by calling `opt.as_deref_mut()` instead + --> $DIR/option_as_ref_deref.rs:43:13 + | +LL | let _ = opt.as_mut().map(|x| &mut **x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using as_deref_mut instead: `opt.as_deref_mut()` + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/option_env_unwrap.rs b/src/tools/clippy/tests/ui/option_env_unwrap.rs new file mode 100644 index 0000000000000..642c77460a340 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_env_unwrap.rs @@ -0,0 +1,23 @@ +// aux-build:macro_rules.rs +#![warn(clippy::option_env_unwrap)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! option_env_unwrap { + ($env: expr) => { + option_env!($env).unwrap() + }; + ($env: expr, $message: expr) => { + option_env!($env).expect($message) + }; +} + +fn main() { + let _ = option_env!("PATH").unwrap(); + let _ = option_env!("PATH").expect("environment variable PATH isn't set"); + let _ = option_env_unwrap!("PATH"); + let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set"); + let _ = option_env_unwrap_external!("PATH"); + let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set"); +} diff --git a/src/tools/clippy/tests/ui/option_env_unwrap.stderr b/src/tools/clippy/tests/ui/option_env_unwrap.stderr new file mode 100644 index 0000000000000..8de9c8a9d29e0 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_env_unwrap.stderr @@ -0,0 +1,61 @@ +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:17:13 + | +LL | let _ = option_env!("PATH").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::option-env-unwrap` implied by `-D warnings` + = help: consider using the `env!` macro instead + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:18:13 + | +LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:9:9 + | +LL | option_env!($env).unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | let _ = option_env_unwrap!("PATH"); + | -------------------------- in this macro invocation + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:12:9 + | +LL | option_env!($env).expect($message) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set"); + | ----------------------------------------------------------------- in this macro invocation + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:21:13 + | +LL | let _ = option_env_unwrap_external!("PATH"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:22:13 + | +LL | let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/option_map_or_none.fixed b/src/tools/clippy/tests/ui/option_map_or_none.fixed new file mode 100644 index 0000000000000..decbae4e5af9a --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_or_none.fixed @@ -0,0 +1,16 @@ +// run-rustfix + +#![allow(clippy::option_and_then_some)] + +fn main() { + let opt = Some(1); + + // Check `OPTION_MAP_OR_NONE`. + // Single line case. + let _ = opt.and_then(|x| Some(x + 1)); + // Multi-line case. + #[rustfmt::skip] + let _ = opt.and_then(|x| { + Some(x + 1) + }); +} diff --git a/src/tools/clippy/tests/ui/option_map_or_none.rs b/src/tools/clippy/tests/ui/option_map_or_none.rs new file mode 100644 index 0000000000000..0f1d2218d5d38 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_or_none.rs @@ -0,0 +1,16 @@ +// run-rustfix + +#![allow(clippy::option_and_then_some)] + +fn main() { + let opt = Some(1); + + // Check `OPTION_MAP_OR_NONE`. + // Single line case. + let _ = opt.map_or(None, |x| Some(x + 1)); + // Multi-line case. + #[rustfmt::skip] + let _ = opt.map_or(None, |x| { + Some(x + 1) + }); +} diff --git a/src/tools/clippy/tests/ui/option_map_or_none.stderr b/src/tools/clippy/tests/ui/option_map_or_none.stderr new file mode 100644 index 0000000000000..6f707987dbcaa --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_or_none.stderr @@ -0,0 +1,26 @@ +error: called `map_or(None, f)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/option_map_or_none.rs:10:13 + | +LL | let _ = opt.map_or(None, |x| Some(x + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `opt.and_then(|x| Some(x + 1))` + | + = note: `-D clippy::option-map-or-none` implied by `-D warnings` + +error: called `map_or(None, f)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/option_map_or_none.rs:13:13 + | +LL | let _ = opt.map_or(None, |x| { + | _____________^ +LL | | Some(x + 1) +LL | | }); + | |_________________________^ + | +help: try using `and_then` instead + | +LL | let _ = opt.and_then(|x| { +LL | Some(x + 1) +LL | }); + | + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed new file mode 100644 index 0000000000000..9a0da404cb6db --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.fixed @@ -0,0 +1,84 @@ +// run-rustfix + +#![warn(clippy::option_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +fn option() -> Option { + Some(10) +} + +struct HasOption { + field: Option, +} + +impl HasOption { + fn do_option_nothing(self: &Self, value: usize) {} + + fn do_option_plus_one(self: &Self, value: usize) -> usize { + value + 1 + } +} +#[rustfmt::skip] +fn option_map_unit_fn() { + let x = HasOption { field: Some(10) }; + + x.field.map(plus_one); + let _ : Option<()> = x.field.map(do_nothing); + + if let Some(x_field) = x.field { do_nothing(x_field) } + + if let Some(x_field) = x.field { do_nothing(x_field) } + + if let Some(x_field) = x.field { diverge(x_field) } + + let captured = 10; + if let Some(value) = x.field { do_nothing(value + captured) }; + let _ : Option<()> = x.field.map(|value| do_nothing(value + captured)); + + if let Some(value) = x.field { x.do_option_nothing(value + captured) } + + if let Some(value) = x.field { x.do_option_plus_one(value + captured); } + + + if let Some(value) = x.field { do_nothing(value + captured) } + + if let Some(value) = x.field { do_nothing(value + captured) } + + if let Some(value) = x.field { do_nothing(value + captured); } + + if let Some(value) = x.field { do_nothing(value + captured); } + + + if let Some(value) = x.field { diverge(value + captured) } + + if let Some(value) = x.field { diverge(value + captured) } + + if let Some(value) = x.field { diverge(value + captured); } + + if let Some(value) = x.field { diverge(value + captured); } + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + if let Some(value) = x.field { let y = plus_one(value + captured); } + + if let Some(value) = x.field { plus_one(value + captured); } + + if let Some(value) = x.field { plus_one(value + captured); } + + + if let Some(ref value) = x.field { do_nothing(value + captured) } + + if let Some(a) = option() { do_nothing(a) }} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs new file mode 100644 index 0000000000000..58041b62df35a --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.rs @@ -0,0 +1,84 @@ +// run-rustfix + +#![warn(clippy::option_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +fn option() -> Option { + Some(10) +} + +struct HasOption { + field: Option, +} + +impl HasOption { + fn do_option_nothing(self: &Self, value: usize) {} + + fn do_option_plus_one(self: &Self, value: usize) -> usize { + value + 1 + } +} +#[rustfmt::skip] +fn option_map_unit_fn() { + let x = HasOption { field: Some(10) }; + + x.field.map(plus_one); + let _ : Option<()> = x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(diverge); + + let captured = 10; + if let Some(value) = x.field { do_nothing(value + captured) }; + let _ : Option<()> = x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| x.do_option_nothing(value + captured)); + + x.field.map(|value| { x.do_option_plus_one(value + captured); }); + + + x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| { do_nothing(value + captured) }); + + x.field.map(|value| { do_nothing(value + captured); }); + + x.field.map(|value| { { do_nothing(value + captured); } }); + + + x.field.map(|value| diverge(value + captured)); + + x.field.map(|value| { diverge(value + captured) }); + + x.field.map(|value| { diverge(value + captured); }); + + x.field.map(|value| { { diverge(value + captured); } }); + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + x.field.map(|value| { let y = plus_one(value + captured); }); + + x.field.map(|value| { plus_one(value + captured); }); + + x.field.map(|value| { { plus_one(value + captured); } }); + + + x.field.map(|ref value| { do_nothing(value + captured) }); + + option().map(do_nothing);} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr new file mode 100644 index 0000000000000..1312c70b6d592 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_fixable.stderr @@ -0,0 +1,148 @@ +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:38:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(x_field) = x.field { do_nothing(x_field) }` + | + = note: `-D clippy::option-map-unit-fn` implied by `-D warnings` + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:40:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(x_field) = x.field { do_nothing(x_field) }` + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:42:5 + | +LL | x.field.map(diverge); + | ^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(x_field) = x.field { diverge(x_field) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:48:5 + | +LL | x.field.map(|value| x.do_option_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { x.do_option_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:50:5 + | +LL | x.field.map(|value| { x.do_option_plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { x.do_option_plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:53:5 + | +LL | x.field.map(|value| do_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:55:5 + | +LL | x.field.map(|value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:57:5 + | +LL | x.field.map(|value| { do_nothing(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:59:5 + | +LL | x.field.map(|value| { { do_nothing(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:62:5 + | +LL | x.field.map(|value| diverge(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:64:5 + | +LL | x.field.map(|value| { diverge(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:66:5 + | +LL | x.field.map(|value| { diverge(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:68:5 + | +LL | x.field.map(|value| { { diverge(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:73:5 + | +LL | x.field.map(|value| { let y = plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { let y = plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:75:5 + | +LL | x.field.map(|value| { plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:77:5 + | +LL | x.field.map(|value| { { plus_one(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:80:5 + | +LL | x.field.map(|ref value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(ref value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type + --> $DIR/option_map_unit_fn_fixable.rs:82:5 + | +LL | option().map(do_nothing);} + | ^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Some(a) = option() { do_nothing(a) }` + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.rs b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.rs new file mode 100644 index 0000000000000..20e6c15b18d5f --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.rs @@ -0,0 +1,39 @@ +#![warn(clippy::option_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +#[rustfmt::skip] +fn option_map_unit_fn() { + + x.field.map(|value| { do_nothing(value); do_nothing(value) }); + + x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + + // Suggestion for the let block should be `{ ... }` as it's too difficult to build a + // proper suggestion for these cases + x.field.map(|value| { + do_nothing(value); + do_nothing(value) + }); + x.field.map(|value| { do_nothing(value); do_nothing(value); }); + + // The following should suggest `if let Some(_X) ...` as it's difficult to generate a proper let variable name for them + Some(42).map(diverge); + "12".parse::().ok().map(diverge); + Some(plus_one(1)).map(do_nothing); + + // Should suggest `if let Some(_y) ...` to not override the existing foo variable + let y = Some(42); + y.map(do_nothing); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.stderr b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.stderr new file mode 100644 index 0000000000000..a53f5889c58da --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unit_fn_unfixable.stderr @@ -0,0 +1,27 @@ +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:17:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:19:5 + | +LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:23:5 + | +LL | x.field.map(|value| { + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/option_map_unit_fn_unfixable.rs:27:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); + | ^ not found in this scope + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/src/tools/clippy/tests/ui/option_map_unwrap_or.rs b/src/tools/clippy/tests/ui/option_map_unwrap_or.rs new file mode 100644 index 0000000000000..0364d83663a06 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unwrap_or.rs @@ -0,0 +1,88 @@ +// FIXME: Add "run-rustfix" once it's supported for multipart suggestions +// aux-build:option_helpers.rs + +#![warn(clippy::option_map_unwrap_or, clippy::option_map_unwrap_or_else)] + +#[macro_use] +extern crate option_helpers; + +use std::collections::HashMap; + +/// Checks implementation of the following lints: +/// * `OPTION_MAP_UNWRAP_OR` +/// * `OPTION_MAP_UNWRAP_OR_ELSE` +#[rustfmt::skip] +fn option_methods() { + let opt = Some(1); + + // Check `OPTION_MAP_UNWRAP_OR`. + // Single line case. + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or(0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or(0); + let _ = opt.map(|x| x + 1) + .unwrap_or({ + 0 + }); + // Single line `map(f).unwrap_or(None)` case. + let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + // Multi-line `map(f).unwrap_or(None)` cases. + let _ = opt.map(|x| { + Some(x + 1) + } + ).unwrap_or(None); + let _ = opt + .map(|x| Some(x + 1)) + .unwrap_or(None); + // macro case + let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint + + // Should not lint if not copyable + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id); + // ...but DO lint if the `unwrap_or` argument is not used in the `map` + let id: String = "identifier".to_string(); + let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + + // Check OPTION_MAP_UNWRAP_OR_ELSE + // single line case + let _ = opt.map(|x| x + 1) + // Should lint even though this call is on a separate line. + .unwrap_or_else(|| 0); + // Multi-line cases. + let _ = opt.map(|x| { + x + 1 + } + ).unwrap_or_else(|| 0); + let _ = opt.map(|x| x + 1) + .unwrap_or_else(|| + 0 + ); + // Macro case. + // Should not lint. + let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0); + + // Issue #4144 + { + let mut frequencies = HashMap::new(); + let word = "foo"; + + frequencies + .get_mut(word) + .map(|count| { + *count += 1; + }) + .unwrap_or_else(|| { + frequencies.insert(word.to_owned(), 1); + }); + } +} + +fn main() { + option_methods(); +} diff --git a/src/tools/clippy/tests/ui/option_map_unwrap_or.stderr b/src/tools/clippy/tests/ui/option_map_unwrap_or.stderr new file mode 100644 index 0000000000000..f05f2893de23a --- /dev/null +++ b/src/tools/clippy/tests/ui/option_map_unwrap_or.stderr @@ -0,0 +1,138 @@ +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/option_map_unwrap_or.rs:20:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or(0); + | |_____________________^ + | + = note: `-D clippy::option-map-unwrap-or` implied by `-D warnings` +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or(0, |x| x + 1); + | ^^^^^^ ^^ -- + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/option_map_unwrap_or.rs:24:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or(0); + | |__________________^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or(0, |x| { +LL | x + 1 +LL | } +LL | ); + | + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/option_map_unwrap_or.rs:28:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or({ +LL | | 0 +LL | | }); + | |__________^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = opt.map_or({ +LL | 0 +LL | }, |x| x + 1); + | + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/option_map_unwrap_or.rs:33:13 + | +LL | let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `and_then(f)` instead + | +LL | let _ = opt.and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/option_map_unwrap_or.rs:35:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | Some(x + 1) +LL | | } +LL | | ).unwrap_or(None); + | |_____________________^ + | +help: use `and_then(f)` instead + | +LL | let _ = opt.and_then(|x| { +LL | Some(x + 1) +LL | } +LL | ); + | + +error: called `map(f).unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then(f)` instead + --> $DIR/option_map_unwrap_or.rs:39:13 + | +LL | let _ = opt + | _____________^ +LL | | .map(|x| Some(x + 1)) +LL | | .unwrap_or(None); + | |________________________^ + | +help: use `and_then(f)` instead + | +LL | .and_then(|x| Some(x + 1)); + | ^^^^^^^^ -- + +error: called `map(f).unwrap_or(a)` on an `Option` value. This can be done more directly by calling `map_or(a, f)` instead + --> $DIR/option_map_unwrap_or.rs:50:13 + | +LL | let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(a, f)` instead + | +LL | let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); + | ^^^^^^ ^^^ -- + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/option_map_unwrap_or.rs:54:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | // Should lint even though this call is on a separate line. +LL | | .unwrap_or_else(|| 0); + | |_____________________________^ + | + = note: `-D clippy::option-map-unwrap-or-else` implied by `-D warnings` + = note: replace `map(|x| x + 1).unwrap_or_else(|| 0)` with `map_or_else(|| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/option_map_unwrap_or.rs:58:13 + | +LL | let _ = opt.map(|x| { + | _____________^ +LL | | x + 1 +LL | | } +LL | | ).unwrap_or_else(|| 0); + | |__________________________^ + +error: called `map(f).unwrap_or_else(g)` on an `Option` value. This can be done more directly by calling `map_or_else(g, f)` instead + --> $DIR/option_map_unwrap_or.rs:62:13 + | +LL | let _ = opt.map(|x| x + 1) + | _____________^ +LL | | .unwrap_or_else(|| +LL | | 0 +LL | | ); + | |_________^ + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/option_option.rs b/src/tools/clippy/tests/ui/option_option.rs new file mode 100644 index 0000000000000..904c50e14039a --- /dev/null +++ b/src/tools/clippy/tests/ui/option_option.rs @@ -0,0 +1,62 @@ +#![deny(clippy::option_option)] + +fn input(_: Option>) {} + +fn output() -> Option> { + None +} + +fn output_nested() -> Vec>> { + vec![None] +} + +// The lint only generates one warning for this +fn output_nested_nested() -> Option>> { + None +} + +struct Struct { + x: Option>, +} + +impl Struct { + fn struct_fn() -> Option> { + None + } +} + +trait Trait { + fn trait_fn() -> Option>; +} + +enum Enum { + Tuple(Option>), + Struct { x: Option> }, +} + +// The lint allows this +type OptionOption = Option>; + +// The lint allows this +fn output_type_alias() -> OptionOption { + None +} + +// The line allows this +impl Trait for Struct { + fn trait_fn() -> Option> { + None + } +} + +fn main() { + input(None); + output(); + output_nested(); + + // The lint allows this + let local: Option> = None; + + // The lint allows this + let expr = Some(Some(true)); +} diff --git a/src/tools/clippy/tests/ui/option_option.stderr b/src/tools/clippy/tests/ui/option_option.stderr new file mode 100644 index 0000000000000..79db186d7ea77 --- /dev/null +++ b/src/tools/clippy/tests/ui/option_option.stderr @@ -0,0 +1,62 @@ +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:3:13 + | +LL | fn input(_: Option>) {} + | ^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/option_option.rs:1:9 + | +LL | #![deny(clippy::option_option)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:5:16 + | +LL | fn output() -> Option> { + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:9:27 + | +LL | fn output_nested() -> Vec>> { + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:14:30 + | +LL | fn output_nested_nested() -> Option>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:19:8 + | +LL | x: Option>, + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:23:23 + | +LL | fn struct_fn() -> Option> { + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:29:22 + | +LL | fn trait_fn() -> Option>; + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:33:11 + | +LL | Tuple(Option>), + | ^^^^^^^^^^^^^^^^^^ + +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:34:17 + | +LL | Struct { x: Option> }, + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/or_fun_call.fixed b/src/tools/clippy/tests/ui/or_fun_call.fixed new file mode 100644 index 0000000000000..8ea03fe42616c --- /dev/null +++ b/src/tools/clippy/tests/ui/or_fun_call.fixed @@ -0,0 +1,110 @@ +// run-rustfix + +#![warn(clippy::or_fun_call)] +#![allow(dead_code)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::time::Duration; + +/// Checks implementation of the `OR_FUN_CALL` lint. +fn or_fun_call() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + enum Enum { + A(i32), + } + + fn make() -> T { + unimplemented!(); + } + + let with_enum = Some(Enum::A(1)); + with_enum.unwrap_or(Enum::A(5)); + + let with_const_fn = Some(Duration::from_secs(1)); + with_const_fn.unwrap_or(Duration::from_secs(5)); + + let with_constructor = Some(vec![1]); + with_constructor.unwrap_or_else(make); + + let with_new = Some(vec![1]); + with_new.unwrap_or_default(); + + let with_const_args = Some(vec![1]); + with_const_args.unwrap_or_else(|| Vec::with_capacity(12)); + + let with_err: Result<_, ()> = Ok(vec![1]); + with_err.unwrap_or_else(|_| make()); + + let with_err_args: Result<_, ()> = Ok(vec![1]); + with_err_args.unwrap_or_else(|_| Vec::with_capacity(12)); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or_default(); + + let with_default_type = Some(1); + with_default_type.unwrap_or_default(); + + let with_vec = Some(vec![1]); + with_vec.unwrap_or_default(); + + let without_default = Some(Foo); + without_default.unwrap_or_else(Foo::new); + + let mut map = HashMap::::new(); + map.entry(42).or_insert_with(String::new); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_insert_with(String::new); + + let stringy = Some(String::from("")); + let _ = stringy.unwrap_or_else(|| "".to_owned()); + + let opt = Some(1); + let hello = "Hello"; + let _ = opt.ok_or(format!("{} world.", hello)); +} + +struct Foo(u8); +struct Bar(String, Duration); +#[rustfmt::skip] +fn test_or_with_ctors() { + let opt = Some(1); + let opt_opt = Some(Some(1)); + // we also test for const promotion, this makes sure we don't hit that + let two = 2; + + let _ = opt_opt.unwrap_or(Some(2)); + let _ = opt_opt.unwrap_or(Some(two)); + let _ = opt.ok_or(Some(2)); + let _ = opt.ok_or(Some(two)); + let _ = opt.ok_or(Foo(2)); + let _ = opt.ok_or(Foo(two)); + let _ = opt.or(Some(2)); + let _ = opt.or(Some(two)); + + let _ = Some("a".to_string()).or_else(|| Some("b".to_string())); + + let b = "b".to_string(); + let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) + .or(Some(Bar(b, Duration::from_secs(2)))); +} + +// Issue 4514 - early return +fn f() -> Option<()> { + let a = Some(1); + let b = 1i32; + + let _ = a.unwrap_or(b.checked_mul(3)?.min(240)); + + Some(()) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.rs b/src/tools/clippy/tests/ui/or_fun_call.rs new file mode 100644 index 0000000000000..7599b945a9137 --- /dev/null +++ b/src/tools/clippy/tests/ui/or_fun_call.rs @@ -0,0 +1,110 @@ +// run-rustfix + +#![warn(clippy::or_fun_call)] +#![allow(dead_code)] + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::time::Duration; + +/// Checks implementation of the `OR_FUN_CALL` lint. +fn or_fun_call() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + enum Enum { + A(i32), + } + + fn make() -> T { + unimplemented!(); + } + + let with_enum = Some(Enum::A(1)); + with_enum.unwrap_or(Enum::A(5)); + + let with_const_fn = Some(Duration::from_secs(1)); + with_const_fn.unwrap_or(Duration::from_secs(5)); + + let with_constructor = Some(vec![1]); + with_constructor.unwrap_or(make()); + + let with_new = Some(vec![1]); + with_new.unwrap_or(Vec::new()); + + let with_const_args = Some(vec![1]); + with_const_args.unwrap_or(Vec::with_capacity(12)); + + let with_err: Result<_, ()> = Ok(vec![1]); + with_err.unwrap_or(make()); + + let with_err_args: Result<_, ()> = Ok(vec![1]); + with_err_args.unwrap_or(Vec::with_capacity(12)); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or(Default::default()); + + let with_default_type = Some(1); + with_default_type.unwrap_or(u64::default()); + + let with_vec = Some(vec![1]); + with_vec.unwrap_or(vec![]); + + let without_default = Some(Foo); + without_default.unwrap_or(Foo::new()); + + let mut map = HashMap::::new(); + map.entry(42).or_insert(String::new()); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_insert(String::new()); + + let stringy = Some(String::from("")); + let _ = stringy.unwrap_or("".to_owned()); + + let opt = Some(1); + let hello = "Hello"; + let _ = opt.ok_or(format!("{} world.", hello)); +} + +struct Foo(u8); +struct Bar(String, Duration); +#[rustfmt::skip] +fn test_or_with_ctors() { + let opt = Some(1); + let opt_opt = Some(Some(1)); + // we also test for const promotion, this makes sure we don't hit that + let two = 2; + + let _ = opt_opt.unwrap_or(Some(2)); + let _ = opt_opt.unwrap_or(Some(two)); + let _ = opt.ok_or(Some(2)); + let _ = opt.ok_or(Some(two)); + let _ = opt.ok_or(Foo(2)); + let _ = opt.ok_or(Foo(two)); + let _ = opt.or(Some(2)); + let _ = opt.or(Some(two)); + + let _ = Some("a".to_string()).or(Some("b".to_string())); + + let b = "b".to_string(); + let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) + .or(Some(Bar(b, Duration::from_secs(2)))); +} + +// Issue 4514 - early return +fn f() -> Option<()> { + let a = Some(1); + let b = 1i32; + + let _ = a.unwrap_or(b.checked_mul(3)?.min(240)); + + Some(()) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/or_fun_call.stderr b/src/tools/clippy/tests/ui/or_fun_call.stderr new file mode 100644 index 0000000000000..96d55771e6cef --- /dev/null +++ b/src/tools/clippy/tests/ui/or_fun_call.stderr @@ -0,0 +1,82 @@ +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:35:22 + | +LL | with_constructor.unwrap_or(make()); + | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(make)` + | + = note: `-D clippy::or-fun-call` implied by `-D warnings` + +error: use of `unwrap_or` followed by a call to `new` + --> $DIR/or_fun_call.rs:38:5 + | +LL | with_new.unwrap_or(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_new.unwrap_or_default()` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:41:21 + | +LL | with_const_args.unwrap_or(Vec::with_capacity(12)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Vec::with_capacity(12))` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:44:14 + | +LL | with_err.unwrap_or(make()); + | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| make())` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:47:19 + | +LL | with_err_args.unwrap_or(Vec::with_capacity(12)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| Vec::with_capacity(12))` + +error: use of `unwrap_or` followed by a call to `default` + --> $DIR/or_fun_call.rs:50:5 + | +LL | with_default_trait.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_trait.unwrap_or_default()` + +error: use of `unwrap_or` followed by a call to `default` + --> $DIR/or_fun_call.rs:53:5 + | +LL | with_default_type.unwrap_or(u64::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_type.unwrap_or_default()` + +error: use of `unwrap_or` followed by a call to `new` + --> $DIR/or_fun_call.rs:56:5 + | +LL | with_vec.unwrap_or(vec![]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_vec.unwrap_or_default()` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:59:21 + | +LL | without_default.unwrap_or(Foo::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)` + +error: use of `or_insert` followed by a function call + --> $DIR/or_fun_call.rs:62:19 + | +LL | map.entry(42).or_insert(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)` + +error: use of `or_insert` followed by a function call + --> $DIR/or_fun_call.rs:65:21 + | +LL | btree.entry(42).or_insert(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:68:21 + | +LL | let _ = stringy.unwrap_or("".to_owned()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "".to_owned())` + +error: use of `or` followed by a function call + --> $DIR/or_fun_call.rs:93:35 + | +LL | let _ = Some("a".to_string()).or(Some("b".to_string())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some("b".to_string()))` + +error: aborting due to 13 previous errors + diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.rs b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.rs new file mode 100644 index 0000000000000..f20a0ede1137c --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.rs @@ -0,0 +1,11 @@ +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, const_err)] + +fn main() { + let x = [1, 2, 3, 4]; + + // issue 3102 + let num = 1; + &x[num..10]; // should trigger out of bounds error + &x[10..num]; // should trigger out of bounds error +} diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.stderr b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.stderr new file mode 100644 index 0000000000000..516c1df40be0a --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/issue-3102.stderr @@ -0,0 +1,16 @@ +error: range is out of bounds + --> $DIR/issue-3102.rs:9:13 + | +LL | &x[num..10]; // should trigger out of bounds error + | ^^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + +error: range is out of bounds + --> $DIR/issue-3102.rs:10:8 + | +LL | &x[10..num]; // should trigger out of bounds error + | ^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.rs b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.rs new file mode 100644 index 0000000000000..590e578d758ea --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.rs @@ -0,0 +1,22 @@ +#![warn(clippy::out_of_bounds_indexing)] +#![allow(clippy::no_effect, clippy::unnecessary_operation, const_err)] + +fn main() { + let x = [1, 2, 3, 4]; + + &x[..=4]; + &x[1..5]; + &x[5..]; + &x[..5]; + &x[5..].iter().map(|x| 2 * x).collect::>(); + &x[0..=4]; + + &x[4..]; // Ok, should not produce stderr. + &x[..4]; // Ok, should not produce stderr. + &x[..]; // Ok, should not produce stderr. + &x[1..]; // Ok, should not produce stderr. + &x[2..].iter().map(|x| 2 * x).collect::>(); // Ok, should not produce stderr. + + &x[0..].get(..3); // Ok, should not produce stderr. + &x[0..3]; // Ok, should not produce stderr. +} diff --git a/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.stderr b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.stderr new file mode 100644 index 0000000000000..3d95afcdab233 --- /dev/null +++ b/src/tools/clippy/tests/ui/out_of_bounds_indexing/simple.stderr @@ -0,0 +1,40 @@ +error: range is out of bounds + --> $DIR/simple.rs:7:11 + | +LL | &x[..=4]; + | ^ + | + = note: `-D clippy::out-of-bounds-indexing` implied by `-D warnings` + +error: range is out of bounds + --> $DIR/simple.rs:8:11 + | +LL | &x[1..5]; + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:9:8 + | +LL | &x[5..]; + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:10:10 + | +LL | &x[..5]; + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:11:8 + | +LL | &x[5..].iter().map(|x| 2 * x).collect::>(); + | ^ + +error: range is out of bounds + --> $DIR/simple.rs:12:12 + | +LL | &x[0..=4]; + | ^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/outer_expn_data.fixed b/src/tools/clippy/tests/ui/outer_expn_data.fixed new file mode 100644 index 0000000000000..999a19b289e18 --- /dev/null +++ b/src/tools/clippy/tests/ui/outer_expn_data.fixed @@ -0,0 +1,28 @@ +// run-rustfix + +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; + +declare_lint! { + pub TEST_LINT, + Warn, + "" +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { + fn check_expr(&mut self, _cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { + let _ = expr.span.ctxt().outer_expn_data(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/outer_expn_data.rs b/src/tools/clippy/tests/ui/outer_expn_data.rs new file mode 100644 index 0000000000000..5405d475d1acc --- /dev/null +++ b/src/tools/clippy/tests/ui/outer_expn_data.rs @@ -0,0 +1,28 @@ +// run-rustfix + +#![deny(clippy::internal)] +#![feature(rustc_private)] + +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +use rustc_hir::Expr; +use rustc_lint::{LateContext, LateLintPass}; + +declare_lint! { + pub TEST_LINT, + Warn, + "" +} + +declare_lint_pass!(Pass => [TEST_LINT]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { + fn check_expr(&mut self, _cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { + let _ = expr.span.ctxt().outer_expn().expn_data(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/outer_expn_data.stderr b/src/tools/clippy/tests/ui/outer_expn_data.stderr new file mode 100644 index 0000000000000..56b6ce1f78ea4 --- /dev/null +++ b/src/tools/clippy/tests/ui/outer_expn_data.stderr @@ -0,0 +1,15 @@ +error: usage of `outer_expn().expn_data()` + --> $DIR/outer_expn_data.rs:24:34 + | +LL | let _ = expr.span.ctxt().outer_expn().expn_data(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()` + | +note: the lint level is defined here + --> $DIR/outer_expn_data.rs:3:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::outer_expn_expn_data)]` implied by `#[deny(clippy::internal)]` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/overflow_check_conditional.rs b/src/tools/clippy/tests/ui/overflow_check_conditional.rs new file mode 100644 index 0000000000000..84332040dbadb --- /dev/null +++ b/src/tools/clippy/tests/ui/overflow_check_conditional.rs @@ -0,0 +1,26 @@ +#![allow(clippy::many_single_char_names)] +#![warn(clippy::overflow_check_conditional)] + +fn main() { + let a: u32 = 1; + let b: u32 = 2; + let c: u32 = 3; + if a + b < a {} + if a > a + b {} + if a + b < b {} + if b > a + b {} + if a - b > b {} + if b < a - b {} + if a - b > a {} + if a < a - b {} + if a + b < c {} + if c > a + b {} + if a - b < c {} + if c > a - b {} + let i = 1.1; + let j = 2.2; + if i + j < i {} + if i - j < i {} + if i > i + j {} + if i - j < i {} +} diff --git a/src/tools/clippy/tests/ui/overflow_check_conditional.stderr b/src/tools/clippy/tests/ui/overflow_check_conditional.stderr new file mode 100644 index 0000000000000..ad66135d326bd --- /dev/null +++ b/src/tools/clippy/tests/ui/overflow_check_conditional.stderr @@ -0,0 +1,52 @@ +error: You are trying to use classic C overflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:8:8 + | +LL | if a + b < a {} + | ^^^^^^^^^ + | + = note: `-D clippy::overflow-check-conditional` implied by `-D warnings` + +error: You are trying to use classic C overflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:9:8 + | +LL | if a > a + b {} + | ^^^^^^^^^ + +error: You are trying to use classic C overflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:10:8 + | +LL | if a + b < b {} + | ^^^^^^^^^ + +error: You are trying to use classic C overflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:11:8 + | +LL | if b > a + b {} + | ^^^^^^^^^ + +error: You are trying to use classic C underflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:12:8 + | +LL | if a - b > b {} + | ^^^^^^^^^ + +error: You are trying to use classic C underflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:13:8 + | +LL | if b < a - b {} + | ^^^^^^^^^ + +error: You are trying to use classic C underflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:14:8 + | +LL | if a - b > a {} + | ^^^^^^^^^ + +error: You are trying to use classic C underflow conditions that will fail in Rust. + --> $DIR/overflow_check_conditional.rs:15:8 + | +LL | if a < a - b {} + | ^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/panic.rs b/src/tools/clippy/tests/ui/panic.rs new file mode 100644 index 0000000000000..6e004aa9a924f --- /dev/null +++ b/src/tools/clippy/tests/ui/panic.rs @@ -0,0 +1,61 @@ +#![warn(clippy::panic_params)] +#![allow(clippy::assertions_on_constants)] +fn missing() { + if true { + panic!("{}"); + } else if false { + panic!("{:?}"); + } else { + assert!(true, "here be missing values: {}"); + } + + panic!("{{{this}}}"); +} + +fn ok_single() { + panic!("foo bar"); +} + +fn ok_inner() { + // Test for #768 + assert!("foo bar".contains(&format!("foo {}", "bar"))); +} + +fn ok_multiple() { + panic!("{}", "This is {ok}"); +} + +fn ok_bracket() { + match 42 { + 1337 => panic!("{so is this"), + 666 => panic!("so is this}"), + _ => panic!("}so is that{"), + } +} + +const ONE: u32 = 1; + +fn ok_nomsg() { + assert!({ 1 == ONE }); + assert!(if 1 == ONE { ONE == 1 } else { false }); +} + +fn ok_escaped() { + panic!("{{ why should this not be ok? }}"); + panic!(" or {{ that ?"); + panic!(" or }} this ?"); + panic!(" {or {{ that ?"); + panic!(" }or }} this ?"); + panic!("{{ test }"); + panic!("{case }}"); +} + +fn main() { + missing(); + ok_single(); + ok_multiple(); + ok_bracket(); + ok_inner(); + ok_nomsg(); + ok_escaped(); +} diff --git a/src/tools/clippy/tests/ui/panic.stderr b/src/tools/clippy/tests/ui/panic.stderr new file mode 100644 index 0000000000000..1f8ff8ccf5575 --- /dev/null +++ b/src/tools/clippy/tests/ui/panic.stderr @@ -0,0 +1,28 @@ +error: you probably are missing some parameter in your format string + --> $DIR/panic.rs:5:16 + | +LL | panic!("{}"); + | ^^^^ + | + = note: `-D clippy::panic-params` implied by `-D warnings` + +error: you probably are missing some parameter in your format string + --> $DIR/panic.rs:7:16 + | +LL | panic!("{:?}"); + | ^^^^^^ + +error: you probably are missing some parameter in your format string + --> $DIR/panic.rs:9:23 + | +LL | assert!(true, "here be missing values: {}"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: you probably are missing some parameter in your format string + --> $DIR/panic.rs:12:12 + | +LL | panic!("{{{this}}}"); + | ^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/panicking_macros.rs b/src/tools/clippy/tests/ui/panicking_macros.rs new file mode 100644 index 0000000000000..dabb695368dba --- /dev/null +++ b/src/tools/clippy/tests/ui/panicking_macros.rs @@ -0,0 +1,33 @@ +#![warn(clippy::unimplemented, clippy::unreachable, clippy::todo, clippy::panic)] +#![allow(clippy::assertions_on_constants)] + +fn panic() { + let a = 2; + panic!(); + let b = a + 2; +} + +fn todo() { + let a = 2; + todo!(); + let b = a + 2; +} + +fn unimplemented() { + let a = 2; + unimplemented!(); + let b = a + 2; +} + +fn unreachable() { + let a = 2; + unreachable!(); + let b = a + 2; +} + +fn main() { + panic(); + todo(); + unimplemented(); + unreachable(); +} diff --git a/src/tools/clippy/tests/ui/panicking_macros.stderr b/src/tools/clippy/tests/ui/panicking_macros.stderr new file mode 100644 index 0000000000000..72319bc7e4584 --- /dev/null +++ b/src/tools/clippy/tests/ui/panicking_macros.stderr @@ -0,0 +1,34 @@ +error: `panic` should not be present in production code + --> $DIR/panicking_macros.rs:6:5 + | +LL | panic!(); + | ^^^^^^^^^ + | + = note: `-D clippy::panic` implied by `-D warnings` + +error: `todo` should not be present in production code + --> $DIR/panicking_macros.rs:12:5 + | +LL | todo!(); + | ^^^^^^^^ + | + = note: `-D clippy::todo` implied by `-D warnings` + +error: `unimplemented` should not be present in production code + --> $DIR/panicking_macros.rs:18:5 + | +LL | unimplemented!(); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unimplemented` implied by `-D warnings` + +error: `unreachable` should not be present in production code + --> $DIR/panicking_macros.rs:24:5 + | +LL | unreachable!(); + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unreachable` implied by `-D warnings` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/partialeq_ne_impl.rs b/src/tools/clippy/tests/ui/partialeq_ne_impl.rs new file mode 100644 index 0000000000000..1338d3c74d554 --- /dev/null +++ b/src/tools/clippy/tests/ui/partialeq_ne_impl.rs @@ -0,0 +1,26 @@ +#![allow(dead_code)] + +struct Foo; + +impl PartialEq for Foo { + fn eq(&self, _: &Foo) -> bool { + true + } + fn ne(&self, _: &Foo) -> bool { + false + } +} + +struct Bar; + +impl PartialEq for Bar { + fn eq(&self, _: &Bar) -> bool { + true + } + #[allow(clippy::partialeq_ne_impl)] + fn ne(&self, _: &Bar) -> bool { + false + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/partialeq_ne_impl.stderr b/src/tools/clippy/tests/ui/partialeq_ne_impl.stderr new file mode 100644 index 0000000000000..b92da4511b48d --- /dev/null +++ b/src/tools/clippy/tests/ui/partialeq_ne_impl.stderr @@ -0,0 +1,12 @@ +error: re-implementing `PartialEq::ne` is unnecessary + --> $DIR/partialeq_ne_impl.rs:9:5 + | +LL | / fn ne(&self, _: &Foo) -> bool { +LL | | false +LL | | } + | |_____^ + | + = note: `-D clippy::partialeq-ne-impl` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/path_buf_push_overwrite.fixed b/src/tools/clippy/tests/ui/path_buf_push_overwrite.fixed new file mode 100644 index 0000000000000..ef8856830fc99 --- /dev/null +++ b/src/tools/clippy/tests/ui/path_buf_push_overwrite.fixed @@ -0,0 +1,8 @@ +// run-rustfix +use std::path::PathBuf; + +#[warn(clippy::all, clippy::path_buf_push_overwrite)] +fn main() { + let mut x = PathBuf::from("/foo"); + x.push("bar"); +} diff --git a/src/tools/clippy/tests/ui/path_buf_push_overwrite.rs b/src/tools/clippy/tests/ui/path_buf_push_overwrite.rs new file mode 100644 index 0000000000000..6e2d483f45410 --- /dev/null +++ b/src/tools/clippy/tests/ui/path_buf_push_overwrite.rs @@ -0,0 +1,8 @@ +// run-rustfix +use std::path::PathBuf; + +#[warn(clippy::all, clippy::path_buf_push_overwrite)] +fn main() { + let mut x = PathBuf::from("/foo"); + x.push("/bar"); +} diff --git a/src/tools/clippy/tests/ui/path_buf_push_overwrite.stderr b/src/tools/clippy/tests/ui/path_buf_push_overwrite.stderr new file mode 100644 index 0000000000000..09b18d71baf93 --- /dev/null +++ b/src/tools/clippy/tests/ui/path_buf_push_overwrite.stderr @@ -0,0 +1,10 @@ +error: Calling `push` with '/' or '/' (file system root) will overwrite the previous path definition + --> $DIR/path_buf_push_overwrite.rs:7:12 + | +LL | x.push("/bar"); + | ^^^^^^ help: try: `"bar"` + | + = note: `-D clippy::path-buf-push-overwrite` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/patterns.fixed b/src/tools/clippy/tests/ui/patterns.fixed new file mode 100644 index 0000000000000..f22388154499a --- /dev/null +++ b/src/tools/clippy/tests/ui/patterns.fixed @@ -0,0 +1,36 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::all)] + +fn main() { + let v = Some(true); + let s = [0, 1, 2, 3, 4]; + match v { + Some(x) => (), + y => (), + } + match v { + Some(x) => (), + y @ None => (), // no error + } + match s { + [x, inside @ .., y] => (), // no error + [..] => (), + } + + let mut mutv = vec![1, 2, 3]; + + // required "ref" left out in suggestion: #5271 + match mutv { + ref mut x => { + x.push(4); + println!("vec: {:?}", x); + }, + ref y if y == &vec![0] => (), + } + + match mutv { + ref x => println!("vec: {:?}", x), + ref y if y == &vec![0] => (), + } +} diff --git a/src/tools/clippy/tests/ui/patterns.rs b/src/tools/clippy/tests/ui/patterns.rs new file mode 100644 index 0000000000000..5848ecd38d98d --- /dev/null +++ b/src/tools/clippy/tests/ui/patterns.rs @@ -0,0 +1,36 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::all)] + +fn main() { + let v = Some(true); + let s = [0, 1, 2, 3, 4]; + match v { + Some(x) => (), + y @ _ => (), + } + match v { + Some(x) => (), + y @ None => (), // no error + } + match s { + [x, inside @ .., y] => (), // no error + [..] => (), + } + + let mut mutv = vec![1, 2, 3]; + + // required "ref" left out in suggestion: #5271 + match mutv { + ref mut x @ _ => { + x.push(4); + println!("vec: {:?}", x); + }, + ref y if y == &vec![0] => (), + } + + match mutv { + ref x @ _ => println!("vec: {:?}", x), + ref y if y == &vec![0] => (), + } +} diff --git a/src/tools/clippy/tests/ui/patterns.stderr b/src/tools/clippy/tests/ui/patterns.stderr new file mode 100644 index 0000000000000..af067580688b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/patterns.stderr @@ -0,0 +1,22 @@ +error: the `y @ _` pattern can be written as just `y` + --> $DIR/patterns.rs:10:9 + | +LL | y @ _ => (), + | ^^^^^ help: try: `y` + | + = note: `-D clippy::redundant-pattern` implied by `-D warnings` + +error: the `x @ _` pattern can be written as just `x` + --> $DIR/patterns.rs:25:9 + | +LL | ref mut x @ _ => { + | ^^^^^^^^^^^^^ help: try: `ref mut x` + +error: the `x @ _` pattern can be written as just `x` + --> $DIR/patterns.rs:33:9 + | +LL | ref x @ _ => println!("vec: {:?}", x), + | ^^^^^^^^^ help: try: `ref x` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/precedence.fixed b/src/tools/clippy/tests/ui/precedence.fixed new file mode 100644 index 0000000000000..17b1f1bd0bf30 --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence.fixed @@ -0,0 +1,53 @@ +// run-rustfix +#![warn(clippy::precedence)] +#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << (2 + 3); + (1 + 2) << 3; + 4 >> (1 + 1); + (1 + 3) >> 2; + 1 ^ (1 - 1); + 3 | (2 - 1); + 3 & (5 - 2); + -(1i32.abs()); + -(1f32.abs()); + + // These should not trigger an error + let _ = (-1i32).abs(); + let _ = (-1f32).abs(); + let _ = -(1i32).abs(); + let _ = -(1f32).abs(); + let _ = -(1i32.abs()); + let _ = -(1f32.abs()); + + // Odd functions shoud not trigger an error + let _ = -1f64.asin(); + let _ = -1f64.asinh(); + let _ = -1f64.atan(); + let _ = -1f64.atanh(); + let _ = -1f64.cbrt(); + let _ = -1f64.fract(); + let _ = -1f64.round(); + let _ = -1f64.signum(); + let _ = -1f64.sin(); + let _ = -1f64.sinh(); + let _ = -1f64.tan(); + let _ = -1f64.tanh(); + let _ = -1f64.to_degrees(); + let _ = -1f64.to_radians(); + + let b = 3; + trip!(b * 8); +} diff --git a/src/tools/clippy/tests/ui/precedence.rs b/src/tools/clippy/tests/ui/precedence.rs new file mode 100644 index 0000000000000..2d0891fd3c20c --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence.rs @@ -0,0 +1,53 @@ +// run-rustfix +#![warn(clippy::precedence)] +#![allow(unused_must_use, clippy::no_effect, clippy::unnecessary_operation)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << 2 + 3; + 1 + 2 << 3; + 4 >> 1 + 1; + 1 + 3 >> 2; + 1 ^ 1 - 1; + 3 | 2 - 1; + 3 & 5 - 2; + -1i32.abs(); + -1f32.abs(); + + // These should not trigger an error + let _ = (-1i32).abs(); + let _ = (-1f32).abs(); + let _ = -(1i32).abs(); + let _ = -(1f32).abs(); + let _ = -(1i32.abs()); + let _ = -(1f32.abs()); + + // Odd functions shoud not trigger an error + let _ = -1f64.asin(); + let _ = -1f64.asinh(); + let _ = -1f64.atan(); + let _ = -1f64.atanh(); + let _ = -1f64.cbrt(); + let _ = -1f64.fract(); + let _ = -1f64.round(); + let _ = -1f64.signum(); + let _ = -1f64.sin(); + let _ = -1f64.sinh(); + let _ = -1f64.tan(); + let _ = -1f64.tanh(); + let _ = -1f64.to_degrees(); + let _ = -1f64.to_radians(); + + let b = 3; + trip!(b * 8); +} diff --git a/src/tools/clippy/tests/ui/precedence.stderr b/src/tools/clippy/tests/ui/precedence.stderr new file mode 100644 index 0000000000000..a2ed5392bfc7c --- /dev/null +++ b/src/tools/clippy/tests/ui/precedence.stderr @@ -0,0 +1,58 @@ +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:17:5 + | +LL | 1 << 2 + 3; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `1 << (2 + 3)` + | + = note: `-D clippy::precedence` implied by `-D warnings` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:18:5 + | +LL | 1 + 2 << 3; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 2) << 3` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:19:5 + | +LL | 4 >> 1 + 1; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `4 >> (1 + 1)` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:20:5 + | +LL | 1 + 3 >> 2; + | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 3) >> 2` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:21:5 + | +LL | 1 ^ 1 - 1; + | ^^^^^^^^^ help: consider parenthesizing your expression: `1 ^ (1 - 1)` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:22:5 + | +LL | 3 | 2 - 1; + | ^^^^^^^^^ help: consider parenthesizing your expression: `3 | (2 - 1)` + +error: operator precedence can trip the unwary + --> $DIR/precedence.rs:23:5 + | +LL | 3 & 5 - 2; + | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:24:5 + | +LL | -1i32.abs(); + | ^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1i32.abs())` + +error: unary minus has lower precedence than method call + --> $DIR/precedence.rs:25:5 + | +LL | -1f32.abs(); + | ^^^^^^^^^^^ help: consider adding parentheses to clarify your intent: `-(1f32.abs())` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/print.rs b/src/tools/clippy/tests/ui/print.rs new file mode 100644 index 0000000000000..366ccc2b3bd58 --- /dev/null +++ b/src/tools/clippy/tests/ui/print.rs @@ -0,0 +1,35 @@ +#![allow(clippy::print_literal, clippy::write_literal)] +#![warn(clippy::print_stdout, clippy::use_debug)] + +use std::fmt::{Debug, Display, Formatter, Result}; + +#[allow(dead_code)] +struct Foo; + +impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{:?}", 43.1415) + } +} + +impl Debug for Foo { + fn fmt(&self, f: &mut Formatter) -> Result { + // ok, we can use `Debug` formatting in `Debug` implementations + write!(f, "{:?}", 42.718) + } +} + +fn main() { + println!("Hello"); + print!("Hello"); + + print!("Hello {}", "World"); + + print!("Hello {:?}", "World"); + + print!("Hello {:#?}", "#orld"); + + assert_eq!(42, 1337); + + vec![1, 2]; +} diff --git a/src/tools/clippy/tests/ui/print.stderr b/src/tools/clippy/tests/ui/print.stderr new file mode 100644 index 0000000000000..208d953262851 --- /dev/null +++ b/src/tools/clippy/tests/ui/print.stderr @@ -0,0 +1,54 @@ +error: use of `Debug`-based formatting + --> $DIR/print.rs:11:19 + | +LL | write!(f, "{:?}", 43.1415) + | ^^^^^^ + | + = note: `-D clippy::use-debug` implied by `-D warnings` + +error: use of `println!` + --> $DIR/print.rs:23:5 + | +LL | println!("Hello"); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-stdout` implied by `-D warnings` + +error: use of `print!` + --> $DIR/print.rs:24:5 + | +LL | print!("Hello"); + | ^^^^^^^^^^^^^^^ + +error: use of `print!` + --> $DIR/print.rs:26:5 + | +LL | print!("Hello {}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `print!` + --> $DIR/print.rs:28:5 + | +LL | print!("Hello {:?}", "World"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `Debug`-based formatting + --> $DIR/print.rs:28:12 + | +LL | print!("Hello {:?}", "World"); + | ^^^^^^^^^^^^ + +error: use of `print!` + --> $DIR/print.rs:30:5 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of `Debug`-based formatting + --> $DIR/print.rs:30:12 + | +LL | print!("Hello {:#?}", "#orld"); + | ^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/print_literal.rs b/src/tools/clippy/tests/ui/print_literal.rs new file mode 100644 index 0000000000000..40ed18e930263 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_literal.rs @@ -0,0 +1,38 @@ +#![warn(clippy::print_literal)] + +fn main() { + // these should be fine + print!("Hello"); + println!("Hello"); + let world = "world"; + println!("Hello {}", world); + println!("Hello {world}", world = world); + println!("3 in hex is {:X}", 3); + println!("2 + 1 = {:.4}", 3); + println!("2 + 1 = {:5.4}", 3); + println!("Debug test {:?}", "hello, world"); + println!("{0:8} {1:>8}", "hello", "world"); + println!("{1:8} {0:>8}", "hello", "world"); + println!("{foo:8} {bar:>8}", foo = "hello", bar = "world"); + println!("{bar:8} {foo:>8}", foo = "hello", bar = "world"); + println!("{number:>width$}", number = 1, width = 6); + println!("{number:>0width$}", number = 1, width = 6); + + // these should throw warnings + println!("{} of {:b} people know binary, the other half doesn't", 1, 2); + print!("Hello {}", "world"); + println!("Hello {} {}", world, "world"); + println!("Hello {}", "world"); + println!("10 / 4 is {}", 2.5); + println!("2 + 1 = {}", 3); + + // positional args don't change the fact + // that we're using a literal -- this should + // throw a warning + println!("{0} {1}", "hello", "world"); + println!("{1} {0}", "hello", "world"); + + // named args shouldn't change anything either + println!("{foo} {bar}", foo = "hello", bar = "world"); + println!("{bar} {foo}", foo = "hello", bar = "world"); +} diff --git a/src/tools/clippy/tests/ui/print_literal.stderr b/src/tools/clippy/tests/ui/print_literal.stderr new file mode 100644 index 0000000000000..fc502e9f71d52 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_literal.stderr @@ -0,0 +1,88 @@ +error: literal with an empty format string + --> $DIR/print_literal.rs:22:71 + | +LL | println!("{} of {:b} people know binary, the other half doesn't", 1, 2); + | ^ + | + = note: `-D clippy::print-literal` implied by `-D warnings` + +error: literal with an empty format string + --> $DIR/print_literal.rs:23:24 + | +LL | print!("Hello {}", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:24:36 + | +LL | println!("Hello {} {}", world, "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:25:26 + | +LL | println!("Hello {}", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:26:30 + | +LL | println!("10 / 4 is {}", 2.5); + | ^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:27:28 + | +LL | println!("2 + 1 = {}", 3); + | ^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:32:25 + | +LL | println!("{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:32:34 + | +LL | println!("{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:33:25 + | +LL | println!("{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:33:34 + | +LL | println!("{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:36:35 + | +LL | println!("{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:36:50 + | +LL | println!("{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:37:35 + | +LL | println!("{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/print_literal.rs:37:50 + | +LL | println!("{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/print_with_newline.rs b/src/tools/clippy/tests/ui/print_with_newline.rs new file mode 100644 index 0000000000000..3f710540e9038 --- /dev/null +++ b/src/tools/clippy/tests/ui/print_with_newline.rs @@ -0,0 +1,51 @@ +// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934 +// // run-rustfix + +#![allow(clippy::print_literal)] +#![warn(clippy::print_with_newline)] + +fn main() { + print!("Hello\n"); + print!("Hello {}\n", "world"); + print!("Hello {} {}\n", "world", "#2"); + print!("{}\n", 1265); + + // these are all fine + print!(""); + print!("Hello"); + println!("Hello"); + println!("Hello\n"); + println!("Hello {}\n", "world"); + print!("Issue\n{}", 1265); + print!("{}", 1265); + print!("\n{}", 1275); + print!("\n\n"); + print!("like eof\n\n"); + print!("Hello {} {}\n\n", "world", "#2"); + println!("\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 + println!("\nbla\n\n"); // #3126 + + // Escaping + print!("\\n"); // #3514 + print!("\\\n"); // should fail + print!("\\\\n"); + + // Raw strings + print!(r"\n"); // #3778 + + // Literal newlines should also fail + print!( + " +" + ); + print!( + r" +" + ); + + // Don't warn on CRLF (#4208) + print!("\r\n"); + print!("foo\r\n"); + print!("\\r\n"); //~ ERROR + print!("foo\rbar\n") // ~ ERROR +} diff --git a/src/tools/clippy/tests/ui/print_with_newline.stderr b/src/tools/clippy/tests/ui/print_with_newline.stderr new file mode 100644 index 0000000000000..05fe88915d6ef --- /dev/null +++ b/src/tools/clippy/tests/ui/print_with_newline.stderr @@ -0,0 +1,110 @@ +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:8:5 + | +LL | print!("Hello/n"); + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::print-with-newline` implied by `-D warnings` +help: use `println!` instead + | +LL | println!("Hello"); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:9:5 + | +LL | print!("Hello {}/n", "world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("Hello {}", "world"); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:10:5 + | +LL | print!("Hello {} {}/n", "world", "#2"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("Hello {} {}", "world", "#2"); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:11:5 + | +LL | print!("{}/n", 1265); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("{}", 1265); + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:30:5 + | +LL | print!("//n"); // should fail + | ^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("/"); // should fail + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:37:5 + | +LL | / print!( +LL | | " +LL | | " +LL | | ); + | |_____^ + | +help: use `println!` instead + | +LL | println!( +LL | "" + | + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:41:5 + | +LL | / print!( +LL | | r" +LL | | " +LL | | ); + | |_____^ + | +help: use `println!` instead + | +LL | println!( +LL | r"" + | + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:49:5 + | +LL | print!("/r/n"); //~ ERROR + | ^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("/r"); //~ ERROR + | ^^^^^^^ -- + +error: using `print!()` with a format string that ends in a single newline + --> $DIR/print_with_newline.rs:50:5 + | +LL | print!("foo/rbar/n") // ~ ERROR + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `println!` instead + | +LL | println!("foo/rbar") // ~ ERROR + | ^^^^^^^ -- + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/println_empty_string.fixed b/src/tools/clippy/tests/ui/println_empty_string.fixed new file mode 100644 index 0000000000000..2b889b62ea991 --- /dev/null +++ b/src/tools/clippy/tests/ui/println_empty_string.fixed @@ -0,0 +1,11 @@ +// run-rustfix +#![allow(clippy::match_single_binding)] + +fn main() { + println!(); + println!(); + + match "a" { + _ => println!(), + } +} diff --git a/src/tools/clippy/tests/ui/println_empty_string.rs b/src/tools/clippy/tests/ui/println_empty_string.rs new file mode 100644 index 0000000000000..890f5f6847603 --- /dev/null +++ b/src/tools/clippy/tests/ui/println_empty_string.rs @@ -0,0 +1,11 @@ +// run-rustfix +#![allow(clippy::match_single_binding)] + +fn main() { + println!(); + println!(""); + + match "a" { + _ => println!(""), + } +} diff --git a/src/tools/clippy/tests/ui/println_empty_string.stderr b/src/tools/clippy/tests/ui/println_empty_string.stderr new file mode 100644 index 0000000000000..23112b8816893 --- /dev/null +++ b/src/tools/clippy/tests/ui/println_empty_string.stderr @@ -0,0 +1,16 @@ +error: using `println!("")` + --> $DIR/println_empty_string.rs:6:5 + | +LL | println!(""); + | ^^^^^^^^^^^^ help: replace it with: `println!()` + | + = note: `-D clippy::println-empty-string` implied by `-D warnings` + +error: using `println!("")` + --> $DIR/println_empty_string.rs:9:14 + | +LL | _ => println!(""), + | ^^^^^^^^^^^^ help: replace it with: `println!()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/proc_macro.rs b/src/tools/clippy/tests/ui/proc_macro.rs new file mode 100644 index 0000000000000..59914b8b8f627 --- /dev/null +++ b/src/tools/clippy/tests/ui/proc_macro.rs @@ -0,0 +1,26 @@ +//! Check that we correctly lint procedural macros. +#![crate_type = "proc-macro"] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[allow(dead_code)] +fn f() { + let _x = 3.14; +} + +#[proc_macro] +pub fn mybangmacro(t: TokenStream) -> TokenStream { + t +} + +#[proc_macro_derive(MyDerivedTrait)] +pub fn myderive(t: TokenStream) -> TokenStream { + t +} + +#[proc_macro_attribute] +pub fn myattribute(t: TokenStream, a: TokenStream) -> TokenStream { + t +} diff --git a/src/tools/clippy/tests/ui/proc_macro.stderr b/src/tools/clippy/tests/ui/proc_macro.stderr new file mode 100644 index 0000000000000..872cbc66af622 --- /dev/null +++ b/src/tools/clippy/tests/ui/proc_macro.stderr @@ -0,0 +1,10 @@ +error: approximate value of `f{32, 64}::consts::PI` found. Consider using it directly + --> $DIR/proc_macro.rs:10:14 + | +LL | let _x = 3.14; + | ^^^^ + | + = note: `#[deny(clippy::approx_constant)]` on by default + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/ptr_arg.rs b/src/tools/clippy/tests/ui/ptr_arg.rs new file mode 100644 index 0000000000000..30f39e9b06398 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_arg.rs @@ -0,0 +1,86 @@ +#![allow(unused, clippy::many_single_char_names, clippy::redundant_clone)] +#![warn(clippy::ptr_arg)] + +use std::borrow::Cow; + +fn do_vec(x: &Vec) { + //Nothing here +} + +fn do_vec_mut(x: &mut Vec) { + // no error here + //Nothing here +} + +fn do_str(x: &String) { + //Nothing here either +} + +fn do_str_mut(x: &mut String) { + // no error here + //Nothing here either +} + +fn main() {} + +trait Foo { + type Item; + fn do_vec(x: &Vec); + fn do_item(x: &Self::Item); +} + +struct Bar; + +// no error, in trait impl (#425) +impl Foo for Bar { + type Item = Vec; + fn do_vec(x: &Vec) {} + fn do_item(x: &Vec) {} +} + +fn cloned(x: &Vec) -> Vec { + let e = x.clone(); + let f = e.clone(); // OK + let g = x; + let h = g.clone(); // Alas, we cannot reliably detect this without following data. + let i = (e).clone(); + x.clone() +} + +fn str_cloned(x: &String) -> String { + let a = x.clone(); + let b = x.clone(); + let c = b.clone(); + let d = a.clone().clone().clone(); + x.clone() +} + +fn false_positive_capacity(x: &Vec, y: &String) { + let a = x.capacity(); + let b = y.clone(); + let c = y.as_str(); +} + +fn false_positive_capacity_too(x: &String) -> String { + if x.capacity() > 1024 { + panic!("Too large!"); + } + x.clone() +} + +#[allow(dead_code)] +fn test_cow_with_ref(c: &Cow<[i32]>) {} + +#[allow(dead_code)] +fn test_cow(c: Cow<[i32]>) { + let _c = c; +} + +trait Foo2 { + fn do_string(&self); +} + +// no error for &self references where self is of type String (#2293) +impl Foo2 for String { + fn do_string(&self) {} +} diff --git a/src/tools/clippy/tests/ui/ptr_arg.stderr b/src/tools/clippy/tests/ui/ptr_arg.stderr new file mode 100644 index 0000000000000..314f23497f971 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_arg.stderr @@ -0,0 +1,89 @@ +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices. + --> $DIR/ptr_arg.rs:6:14 + | +LL | fn do_vec(x: &Vec) { + | ^^^^^^^^^ help: change this to: `&[i64]` + | + = note: `-D clippy::ptr-arg` implied by `-D warnings` + +error: writing `&String` instead of `&str` involves a new object where a slice will do. + --> $DIR/ptr_arg.rs:15:14 + | +LL | fn do_str(x: &String) { + | ^^^^^^^ help: change this to: `&str` + +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices. + --> $DIR/ptr_arg.rs:28:18 + | +LL | fn do_vec(x: &Vec); + | ^^^^^^^^^ help: change this to: `&[i64]` + +error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices. + --> $DIR/ptr_arg.rs:41:14 + | +LL | fn cloned(x: &Vec) -> Vec { + | ^^^^^^^^ + | +help: change this to + | +LL | fn cloned(x: &[u8]) -> Vec { + | ^^^^^ +help: change `x.clone()` to + | +LL | let e = x.to_owned(); + | ^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | x.to_owned() + | + +error: writing `&String` instead of `&str` involves a new object where a slice will do. + --> $DIR/ptr_arg.rs:50:18 + | +LL | fn str_cloned(x: &String) -> String { + | ^^^^^^^ + | +help: change this to + | +LL | fn str_cloned(x: &str) -> String { + | ^^^^ +help: change `x.clone()` to + | +LL | let a = x.to_string(); + | ^^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | let b = x.to_string(); + | ^^^^^^^^^^^^^ +help: change `x.clone()` to + | +LL | x.to_string() + | + +error: writing `&String` instead of `&str` involves a new object where a slice will do. + --> $DIR/ptr_arg.rs:58:44 + | +LL | fn false_positive_capacity(x: &Vec, y: &String) { + | ^^^^^^^ + | +help: change this to + | +LL | fn false_positive_capacity(x: &Vec, y: &str) { + | ^^^^ +help: change `y.clone()` to + | +LL | let b = y.to_string(); + | ^^^^^^^^^^^^^ +help: change `y.as_str()` to + | +LL | let c = y; + | ^ + +error: using a reference to `Cow` is not recommended. + --> $DIR/ptr_arg.rs:72:25 + | +LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} + | ^^^^^^^^^^^ help: change this to: `&[i32]` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/ptr_offset_with_cast.fixed b/src/tools/clippy/tests/ui/ptr_offset_with_cast.fixed new file mode 100644 index 0000000000000..ebdd6c4003d19 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_offset_with_cast.fixed @@ -0,0 +1,20 @@ +// run-rustfix + +fn main() { + let vec = vec![b'a', b'b', b'c']; + let ptr = vec.as_ptr(); + + let offset_u8 = 1_u8; + let offset_usize = 1_usize; + let offset_isize = 1_isize; + + unsafe { + ptr.add(offset_usize); + ptr.offset(offset_isize as isize); + ptr.offset(offset_u8 as isize); + + ptr.wrapping_add(offset_usize); + ptr.wrapping_offset(offset_isize as isize); + ptr.wrapping_offset(offset_u8 as isize); + } +} diff --git a/src/tools/clippy/tests/ui/ptr_offset_with_cast.rs b/src/tools/clippy/tests/ui/ptr_offset_with_cast.rs new file mode 100644 index 0000000000000..3416c4b727a53 --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_offset_with_cast.rs @@ -0,0 +1,20 @@ +// run-rustfix + +fn main() { + let vec = vec![b'a', b'b', b'c']; + let ptr = vec.as_ptr(); + + let offset_u8 = 1_u8; + let offset_usize = 1_usize; + let offset_isize = 1_isize; + + unsafe { + ptr.offset(offset_usize as isize); + ptr.offset(offset_isize as isize); + ptr.offset(offset_u8 as isize); + + ptr.wrapping_offset(offset_usize as isize); + ptr.wrapping_offset(offset_isize as isize); + ptr.wrapping_offset(offset_u8 as isize); + } +} diff --git a/src/tools/clippy/tests/ui/ptr_offset_with_cast.stderr b/src/tools/clippy/tests/ui/ptr_offset_with_cast.stderr new file mode 100644 index 0000000000000..b5c7a03e2775e --- /dev/null +++ b/src/tools/clippy/tests/ui/ptr_offset_with_cast.stderr @@ -0,0 +1,16 @@ +error: use of `offset` with a `usize` casted to an `isize` + --> $DIR/ptr_offset_with_cast.rs:12:9 + | +LL | ptr.offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)` + | + = note: `-D clippy::ptr-offset-with-cast` implied by `-D warnings` + +error: use of `wrapping_offset` with a `usize` casted to an `isize` + --> $DIR/ptr_offset_with_cast.rs:16:9 + | +LL | ptr.wrapping_offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/question_mark.fixed b/src/tools/clippy/tests/ui/question_mark.fixed new file mode 100644 index 0000000000000..11dff94a28865 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark.fixed @@ -0,0 +1,125 @@ +// run-rustfix +#![allow(unreachable_code)] + +fn some_func(a: Option) -> Option { + a?; + + a +} + +fn some_other_func(a: Option) -> Option { + if a.is_none() { + return None; + } else { + return Some(0); + } + unreachable!() +} + +pub enum SeemsOption { + Some(T), + None, +} + +impl SeemsOption { + pub fn is_none(&self) -> bool { + match *self { + SeemsOption::None => true, + SeemsOption::Some(_) => false, + } + } +} + +fn returns_something_similar_to_option(a: SeemsOption) -> SeemsOption { + if a.is_none() { + return SeemsOption::None; + } + + a +} + +pub struct CopyStruct { + pub opt: Option, +} + +impl CopyStruct { + #[rustfmt::skip] + pub fn func(&self) -> Option { + (self.opt)?; + + self.opt?; + + let _ = Some(self.opt?); + + let _ = self.opt?; + + self.opt + } +} + +#[derive(Clone)] +pub struct MoveStruct { + pub opt: Option>, +} + +impl MoveStruct { + pub fn ref_func(&self) -> Option> { + self.opt.as_ref()?; + + self.opt.clone() + } + + pub fn mov_func_reuse(self) -> Option> { + self.opt.as_ref()?; + + self.opt + } + + pub fn mov_func_no_use(self) -> Option> { + self.opt.as_ref()?; + Some(Vec::new()) + } + + pub fn if_let_ref_func(self) -> Option> { + let v: &Vec<_> = self.opt.as_ref()?; + + Some(v.clone()) + } + + pub fn if_let_mov_func(self) -> Option> { + let v = self.opt?; + + Some(v) + } +} + +fn func() -> Option { + fn f() -> Option { + Some(String::new()) + } + + f()?; + + Some(0) +} + +fn main() { + some_func(Some(42)); + some_func(None); + some_other_func(Some(42)); + + let copy_struct = CopyStruct { opt: Some(54) }; + copy_struct.func(); + + let move_struct = MoveStruct { + opt: Some(vec![42, 1337]), + }; + move_struct.ref_func(); + move_struct.clone().mov_func_reuse(); + move_struct.mov_func_no_use(); + + let so = SeemsOption::Some(45); + returns_something_similar_to_option(so); + + func(); +} diff --git a/src/tools/clippy/tests/ui/question_mark.rs b/src/tools/clippy/tests/ui/question_mark.rs new file mode 100644 index 0000000000000..1d0ee82b4f778 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark.rs @@ -0,0 +1,155 @@ +// run-rustfix +#![allow(unreachable_code)] + +fn some_func(a: Option) -> Option { + if a.is_none() { + return None; + } + + a +} + +fn some_other_func(a: Option) -> Option { + if a.is_none() { + return None; + } else { + return Some(0); + } + unreachable!() +} + +pub enum SeemsOption { + Some(T), + None, +} + +impl SeemsOption { + pub fn is_none(&self) -> bool { + match *self { + SeemsOption::None => true, + SeemsOption::Some(_) => false, + } + } +} + +fn returns_something_similar_to_option(a: SeemsOption) -> SeemsOption { + if a.is_none() { + return SeemsOption::None; + } + + a +} + +pub struct CopyStruct { + pub opt: Option, +} + +impl CopyStruct { + #[rustfmt::skip] + pub fn func(&self) -> Option { + if (self.opt).is_none() { + return None; + } + + if self.opt.is_none() { + return None + } + + let _ = if self.opt.is_none() { + return None; + } else { + self.opt + }; + + let _ = if let Some(x) = self.opt { + x + } else { + return None; + }; + + self.opt + } +} + +#[derive(Clone)] +pub struct MoveStruct { + pub opt: Option>, +} + +impl MoveStruct { + pub fn ref_func(&self) -> Option> { + if self.opt.is_none() { + return None; + } + + self.opt.clone() + } + + pub fn mov_func_reuse(self) -> Option> { + if self.opt.is_none() { + return None; + } + + self.opt + } + + pub fn mov_func_no_use(self) -> Option> { + if self.opt.is_none() { + return None; + } + Some(Vec::new()) + } + + pub fn if_let_ref_func(self) -> Option> { + let v: &Vec<_> = if let Some(ref v) = self.opt { + v + } else { + return None; + }; + + Some(v.clone()) + } + + pub fn if_let_mov_func(self) -> Option> { + let v = if let Some(v) = self.opt { + v + } else { + return None; + }; + + Some(v) + } +} + +fn func() -> Option { + fn f() -> Option { + Some(String::new()) + } + + if f().is_none() { + return None; + } + + Some(0) +} + +fn main() { + some_func(Some(42)); + some_func(None); + some_other_func(Some(42)); + + let copy_struct = CopyStruct { opt: Some(54) }; + copy_struct.func(); + + let move_struct = MoveStruct { + opt: Some(vec![42, 1337]), + }; + move_struct.ref_func(); + move_struct.clone().mov_func_reuse(); + move_struct.mov_func_no_use(); + + let so = SeemsOption::Some(45); + returns_something_similar_to_option(so); + + func(); +} diff --git a/src/tools/clippy/tests/ui/question_mark.stderr b/src/tools/clippy/tests/ui/question_mark.stderr new file mode 100644 index 0000000000000..502615fb175a1 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark.stderr @@ -0,0 +1,104 @@ +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:5:5 + | +LL | / if a.is_none() { +LL | | return None; +LL | | } + | |_____^ help: replace it with: `a?;` + | + = note: `-D clippy::question-mark` implied by `-D warnings` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:50:9 + | +LL | / if (self.opt).is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `(self.opt)?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:54:9 + | +LL | / if self.opt.is_none() { +LL | | return None +LL | | } + | |_________^ help: replace it with: `self.opt?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:58:17 + | +LL | let _ = if self.opt.is_none() { + | _________________^ +LL | | return None; +LL | | } else { +LL | | self.opt +LL | | }; + | |_________^ help: replace it with: `Some(self.opt?)` + +error: this if-let-else may be rewritten with the `?` operator + --> $DIR/question_mark.rs:64:17 + | +LL | let _ = if let Some(x) = self.opt { + | _________________^ +LL | | x +LL | | } else { +LL | | return None; +LL | | }; + | |_________^ help: replace it with: `self.opt?` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:81:9 + | +LL | / if self.opt.is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `self.opt.as_ref()?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:89:9 + | +LL | / if self.opt.is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `self.opt.as_ref()?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:97:9 + | +LL | / if self.opt.is_none() { +LL | | return None; +LL | | } + | |_________^ help: replace it with: `self.opt.as_ref()?;` + +error: this if-let-else may be rewritten with the `?` operator + --> $DIR/question_mark.rs:104:26 + | +LL | let v: &Vec<_> = if let Some(ref v) = self.opt { + | __________________________^ +LL | | v +LL | | } else { +LL | | return None; +LL | | }; + | |_________^ help: replace it with: `self.opt.as_ref()?` + +error: this if-let-else may be rewritten with the `?` operator + --> $DIR/question_mark.rs:114:17 + | +LL | let v = if let Some(v) = self.opt { + | _________________^ +LL | | v +LL | | } else { +LL | | return None; +LL | | }; + | |_________^ help: replace it with: `self.opt?` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:129:5 + | +LL | / if f().is_none() { +LL | | return None; +LL | | } + | |_____^ help: replace it with: `f()?;` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/range.rs b/src/tools/clippy/tests/ui/range.rs new file mode 100644 index 0000000000000..628282509c1a5 --- /dev/null +++ b/src/tools/clippy/tests/ui/range.rs @@ -0,0 +1,16 @@ +#[warn(clippy::range_zip_with_len)] +fn main() { + let v1 = vec![1, 2, 3]; + let v2 = vec![4, 5]; + let _x = v1.iter().zip(0..v1.len()); + let _y = v1.iter().zip(0..v2.len()); // No error +} + +#[allow(unused)] +fn no_panic_with_fake_range_types() { + struct Range { + foo: i32, + } + + let _ = Range { foo: 0 }; +} diff --git a/src/tools/clippy/tests/ui/range.stderr b/src/tools/clippy/tests/ui/range.stderr new file mode 100644 index 0000000000000..d53c1edecac01 --- /dev/null +++ b/src/tools/clippy/tests/ui/range.stderr @@ -0,0 +1,10 @@ +error: It is more idiomatic to use `v1.iter().enumerate()` + --> $DIR/range.rs:5:14 + | +LL | let _x = v1.iter().zip(0..v1.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::range-zip-with-len` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.fixed b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed new file mode 100644 index 0000000000000..6b40211409974 --- /dev/null +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.fixed @@ -0,0 +1,41 @@ +// run-rustfix + +#![allow(unused_parens)] + +fn f() -> usize { + 42 +} + +#[warn(clippy::range_plus_one)] +fn main() { + for _ in 0..2 {} + for _ in 0..=2 {} + + for _ in 0..=3 {} + for _ in 0..=3 + 1 {} + + for _ in 0..=5 {} + for _ in 0..=1 + 5 {} + + for _ in 1..=1 {} + for _ in 1..=1 + 1 {} + + for _ in 0..13 + 13 {} + for _ in 0..=13 - 7 {} + + for _ in 0..=f() {} + for _ in 0..=(1 + f()) {} + + let _ = ..11 - 1; + let _ = ..11; + let _ = ..11; + let _ = (1..=11); + let _ = ((f() + 1)..=f()); + + const ONE: usize = 1; + // integer consts are linted, too + for _ in 1..=ONE {} + + let mut vec: Vec<()> = std::vec::Vec::new(); + vec.drain(..); +} diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.rs b/src/tools/clippy/tests/ui/range_plus_minus_one.rs new file mode 100644 index 0000000000000..3cfed4125b35c --- /dev/null +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.rs @@ -0,0 +1,41 @@ +// run-rustfix + +#![allow(unused_parens)] + +fn f() -> usize { + 42 +} + +#[warn(clippy::range_plus_one)] +fn main() { + for _ in 0..2 {} + for _ in 0..=2 {} + + for _ in 0..3 + 1 {} + for _ in 0..=3 + 1 {} + + for _ in 0..1 + 5 {} + for _ in 0..=1 + 5 {} + + for _ in 1..1 + 1 {} + for _ in 1..=1 + 1 {} + + for _ in 0..13 + 13 {} + for _ in 0..=13 - 7 {} + + for _ in 0..(1 + f()) {} + for _ in 0..=(1 + f()) {} + + let _ = ..11 - 1; + let _ = ..=11 - 1; + let _ = ..=(11 - 1); + let _ = (1..11 + 1); + let _ = (f() + 1)..(f() + 1); + + const ONE: usize = 1; + // integer consts are linted, too + for _ in 1..ONE + ONE {} + + let mut vec: Vec<()> = std::vec::Vec::new(); + vec.drain(..); +} diff --git a/src/tools/clippy/tests/ui/range_plus_minus_one.stderr b/src/tools/clippy/tests/ui/range_plus_minus_one.stderr new file mode 100644 index 0000000000000..f72943a04f252 --- /dev/null +++ b/src/tools/clippy/tests/ui/range_plus_minus_one.stderr @@ -0,0 +1,60 @@ +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:14:14 + | +LL | for _ in 0..3 + 1 {} + | ^^^^^^^^ help: use: `0..=3` + | + = note: `-D clippy::range-plus-one` implied by `-D warnings` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:17:14 + | +LL | for _ in 0..1 + 5 {} + | ^^^^^^^^ help: use: `0..=5` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:20:14 + | +LL | for _ in 1..1 + 1 {} + | ^^^^^^^^ help: use: `1..=1` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:26:14 + | +LL | for _ in 0..(1 + f()) {} + | ^^^^^^^^^^^^ help: use: `0..=f()` + +error: an exclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:30:13 + | +LL | let _ = ..=11 - 1; + | ^^^^^^^^^ help: use: `..11` + | + = note: `-D clippy::range-minus-one` implied by `-D warnings` + +error: an exclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:31:13 + | +LL | let _ = ..=(11 - 1); + | ^^^^^^^^^^^ help: use: `..11` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:32:13 + | +LL | let _ = (1..11 + 1); + | ^^^^^^^^^^^ help: use: `(1..=11)` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:33:13 + | +LL | let _ = (f() + 1)..(f() + 1); + | ^^^^^^^^^^^^^^^^^^^^ help: use: `((f() + 1)..=f())` + +error: an inclusive range would be more readable + --> $DIR/range_plus_minus_one.rs:37:14 + | +LL | for _ in 1..ONE + ONE {} + | ^^^^^^^^^^^^ help: use: `1..=ONE` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_allocation.fixed b/src/tools/clippy/tests/ui/redundant_allocation.fixed new file mode 100644 index 0000000000000..266358334587d --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_allocation.fixed @@ -0,0 +1,48 @@ +// run-rustfix +#![warn(clippy::all)] +#![allow(clippy::boxed_local, clippy::needless_pass_by_value)] +#![allow(clippy::blacklisted_name, unused_variables, dead_code)] + +use std::boxed::Box; +use std::rc::Rc; + +pub struct MyStruct {} + +pub struct SubT { + foo: T, +} + +pub enum MyEnum { + One, + Two, +} + +// Rc<&T> + +pub fn test1(foo: &T) {} + +pub fn test2(foo: &MyStruct) {} + +pub fn test3(foo: &MyEnum) {} + +pub fn test4_neg(foo: Rc>) {} + +// Rc> + +pub fn test5(a: Rc) {} + +// Rc> + +pub fn test6(a: Box) {} + +// Box<&T> + +pub fn test7(foo: &T) {} + +pub fn test8(foo: &MyStruct) {} + +pub fn test9(foo: &MyEnum) {} + +pub fn test10_neg(foo: Box>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_allocation.rs b/src/tools/clippy/tests/ui/redundant_allocation.rs new file mode 100644 index 0000000000000..677b3e56d4dce --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_allocation.rs @@ -0,0 +1,48 @@ +// run-rustfix +#![warn(clippy::all)] +#![allow(clippy::boxed_local, clippy::needless_pass_by_value)] +#![allow(clippy::blacklisted_name, unused_variables, dead_code)] + +use std::boxed::Box; +use std::rc::Rc; + +pub struct MyStruct {} + +pub struct SubT { + foo: T, +} + +pub enum MyEnum { + One, + Two, +} + +// Rc<&T> + +pub fn test1(foo: Rc<&T>) {} + +pub fn test2(foo: Rc<&MyStruct>) {} + +pub fn test3(foo: Rc<&MyEnum>) {} + +pub fn test4_neg(foo: Rc>) {} + +// Rc> + +pub fn test5(a: Rc>) {} + +// Rc> + +pub fn test6(a: Rc>) {} + +// Box<&T> + +pub fn test7(foo: Box<&T>) {} + +pub fn test8(foo: Box<&MyStruct>) {} + +pub fn test9(foo: Box<&MyEnum>) {} + +pub fn test10_neg(foo: Box>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_allocation.stderr b/src/tools/clippy/tests/ui/redundant_allocation.stderr new file mode 100644 index 0000000000000..eaa57ce3024b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_allocation.stderr @@ -0,0 +1,52 @@ +error: usage of `Rc<&T>` + --> $DIR/redundant_allocation.rs:22:22 + | +LL | pub fn test1(foo: Rc<&T>) {} + | ^^^^^^ help: try: `&T` + | + = note: `-D clippy::redundant-allocation` implied by `-D warnings` + +error: usage of `Rc<&T>` + --> $DIR/redundant_allocation.rs:24:19 + | +LL | pub fn test2(foo: Rc<&MyStruct>) {} + | ^^^^^^^^^^^^^ help: try: `&MyStruct` + +error: usage of `Rc<&T>` + --> $DIR/redundant_allocation.rs:26:19 + | +LL | pub fn test3(foo: Rc<&MyEnum>) {} + | ^^^^^^^^^^^ help: try: `&MyEnum` + +error: usage of `Rc>` + --> $DIR/redundant_allocation.rs:32:17 + | +LL | pub fn test5(a: Rc>) {} + | ^^^^^^^^^^^^ help: try: `Rc` + +error: usage of `Rc>` + --> $DIR/redundant_allocation.rs:36:17 + | +LL | pub fn test6(a: Rc>) {} + | ^^^^^^^^^^^^^ help: try: `Box` + +error: usage of `Box<&T>` + --> $DIR/redundant_allocation.rs:40:22 + | +LL | pub fn test7(foo: Box<&T>) {} + | ^^^^^^^ help: try: `&T` + +error: usage of `Box<&T>` + --> $DIR/redundant_allocation.rs:42:19 + | +LL | pub fn test8(foo: Box<&MyStruct>) {} + | ^^^^^^^^^^^^^^ help: try: `&MyStruct` + +error: usage of `Box<&T>` + --> $DIR/redundant_allocation.rs:44:19 + | +LL | pub fn test9(foo: Box<&MyEnum>) {} + | ^^^^^^^^^^^^ help: try: `&MyEnum` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_clone.fixed b/src/tools/clippy/tests/ui/redundant_clone.fixed new file mode 100644 index 0000000000000..764c10a6d398f --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_clone.fixed @@ -0,0 +1,172 @@ +// run-rustfix +// rustfix-only-machine-applicable + +use std::ffi::OsString; +use std::path::Path; + +fn main() { + let _s = ["lorem", "ipsum"].join(" "); + + let s = String::from("foo"); + let _s = s; + + let s = String::from("foo"); + let _s = s; + + let s = String::from("foo"); + let _s = s; + + let _s = Path::new("/a/b/").join("c"); + + let _s = Path::new("/a/b/").join("c"); + + let _s = OsString::new(); + + let _s = OsString::new(); + + // Check that lint level works + #[allow(clippy::redundant_clone)] + let _s = String::new().to_string(); + + let tup = (String::from("foo"),); + let _t = tup.0; + + let tup_ref = &(String::from("foo"),); + let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed + + { + let x = String::new(); + let y = &x; + + let _x = x.clone(); // ok; `x` is borrowed by `y` + + let _ = y.len(); + } + + let x = (String::new(),); + let _ = Some(String::new()).unwrap_or_else(|| x.0.clone()); // ok; closure borrows `x` + + with_branch(Alpha, true); + cannot_double_move(Alpha); + cannot_move_from_type_with_drop(); + borrower_propagation(); + not_consumed(); + issue_5405(); +} + +#[derive(Clone)] +struct Alpha; +fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) { + if b { + (a.clone(), a) + } else { + (Alpha, a) + } +} + +fn cannot_double_move(a: Alpha) -> (Alpha, Alpha) { + (a.clone(), a) +} + +struct TypeWithDrop { + x: String, +} + +impl Drop for TypeWithDrop { + fn drop(&mut self) {} +} + +fn cannot_move_from_type_with_drop() -> String { + let s = TypeWithDrop { x: String::new() }; + s.x.clone() // removing this `clone()` summons E0509 +} + +fn borrower_propagation() { + let s = String::new(); + let t = String::new(); + + { + fn b() -> bool { + unimplemented!() + } + let _u = if b() { &s } else { &t }; + + // ok; `s` and `t` are possibly borrowed + let _s = s.clone(); + let _t = t.clone(); + } + + { + let _u = || s.len(); + let _v = [&t; 32]; + let _s = s.clone(); // ok + let _t = t.clone(); // ok + } + + { + let _u = { + let u = Some(&s); + let _ = s.clone(); // ok + u + }; + let _s = s.clone(); // ok + } + + { + use std::convert::identity as id; + let _u = id(id(&s)); + let _s = s.clone(); // ok, `u` borrows `s` + } + + let _s = s; + let _t = t; + + #[derive(Clone)] + struct Foo { + x: usize, + } + + { + let f = Foo { x: 123 }; + let _x = Some(f.x); + let _f = f; + } + + { + let f = Foo { x: 123 }; + let _x = &f.x; + let _f = f.clone(); // ok + } +} + +fn not_consumed() { + let x = std::path::PathBuf::from("home"); + let y = x.join("matthias"); + // join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is + // redundant. (It also does not consume the PathBuf) + + println!("x: {:?}, y: {:?}", x, y); + + let mut s = String::new(); + s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior. + s.push_str("bar"); + assert_eq!(s, "bar"); + + let t = Some(s); + // OK + if let Some(x) = t.clone() { + println!("{}", x); + } + if let Some(x) = t { + println!("{}", x); + } +} + +#[allow(clippy::clone_on_copy)] +fn issue_5405() { + let a: [String; 1] = [String::from("foo")]; + let _b: String = a[0].clone(); + + let c: [usize; 2] = [2, 3]; + let _d: usize = c[1].clone(); +} diff --git a/src/tools/clippy/tests/ui/redundant_clone.rs b/src/tools/clippy/tests/ui/redundant_clone.rs new file mode 100644 index 0000000000000..839747b131d77 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_clone.rs @@ -0,0 +1,172 @@ +// run-rustfix +// rustfix-only-machine-applicable + +use std::ffi::OsString; +use std::path::Path; + +fn main() { + let _s = ["lorem", "ipsum"].join(" ").to_string(); + + let s = String::from("foo"); + let _s = s.clone(); + + let s = String::from("foo"); + let _s = s.to_string(); + + let s = String::from("foo"); + let _s = s.to_owned(); + + let _s = Path::new("/a/b/").join("c").to_owned(); + + let _s = Path::new("/a/b/").join("c").to_path_buf(); + + let _s = OsString::new().to_owned(); + + let _s = OsString::new().to_os_string(); + + // Check that lint level works + #[allow(clippy::redundant_clone)] + let _s = String::new().to_string(); + + let tup = (String::from("foo"),); + let _t = tup.0.clone(); + + let tup_ref = &(String::from("foo"),); + let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed + + { + let x = String::new(); + let y = &x; + + let _x = x.clone(); // ok; `x` is borrowed by `y` + + let _ = y.len(); + } + + let x = (String::new(),); + let _ = Some(String::new()).unwrap_or_else(|| x.0.clone()); // ok; closure borrows `x` + + with_branch(Alpha, true); + cannot_double_move(Alpha); + cannot_move_from_type_with_drop(); + borrower_propagation(); + not_consumed(); + issue_5405(); +} + +#[derive(Clone)] +struct Alpha; +fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) { + if b { + (a.clone(), a.clone()) + } else { + (Alpha, a) + } +} + +fn cannot_double_move(a: Alpha) -> (Alpha, Alpha) { + (a.clone(), a) +} + +struct TypeWithDrop { + x: String, +} + +impl Drop for TypeWithDrop { + fn drop(&mut self) {} +} + +fn cannot_move_from_type_with_drop() -> String { + let s = TypeWithDrop { x: String::new() }; + s.x.clone() // removing this `clone()` summons E0509 +} + +fn borrower_propagation() { + let s = String::new(); + let t = String::new(); + + { + fn b() -> bool { + unimplemented!() + } + let _u = if b() { &s } else { &t }; + + // ok; `s` and `t` are possibly borrowed + let _s = s.clone(); + let _t = t.clone(); + } + + { + let _u = || s.len(); + let _v = [&t; 32]; + let _s = s.clone(); // ok + let _t = t.clone(); // ok + } + + { + let _u = { + let u = Some(&s); + let _ = s.clone(); // ok + u + }; + let _s = s.clone(); // ok + } + + { + use std::convert::identity as id; + let _u = id(id(&s)); + let _s = s.clone(); // ok, `u` borrows `s` + } + + let _s = s.clone(); + let _t = t.clone(); + + #[derive(Clone)] + struct Foo { + x: usize, + } + + { + let f = Foo { x: 123 }; + let _x = Some(f.x); + let _f = f.clone(); + } + + { + let f = Foo { x: 123 }; + let _x = &f.x; + let _f = f.clone(); // ok + } +} + +fn not_consumed() { + let x = std::path::PathBuf::from("home"); + let y = x.clone().join("matthias"); + // join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is + // redundant. (It also does not consume the PathBuf) + + println!("x: {:?}, y: {:?}", x, y); + + let mut s = String::new(); + s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior. + s.push_str("bar"); + assert_eq!(s, "bar"); + + let t = Some(s); + // OK + if let Some(x) = t.clone() { + println!("{}", x); + } + if let Some(x) = t { + println!("{}", x); + } +} + +#[allow(clippy::clone_on_copy)] +fn issue_5405() { + let a: [String; 1] = [String::from("foo")]; + let _b: String = a[0].clone(); + + let c: [usize; 2] = [2, 3]; + let _d: usize = c[1].clone(); +} diff --git a/src/tools/clippy/tests/ui/redundant_clone.stderr b/src/tools/clippy/tests/ui/redundant_clone.stderr new file mode 100644 index 0000000000000..eced198283ce8 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_clone.stderr @@ -0,0 +1,171 @@ +error: redundant clone + --> $DIR/redundant_clone.rs:8:42 + | +LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); + | ^^^^^^^^^^^^ help: remove this + | + = note: `-D clippy::redundant-clone` implied by `-D warnings` +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:8:14 + | +LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:11:15 + | +LL | let _s = s.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:11:14 + | +LL | let _s = s.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:14:15 + | +LL | let _s = s.to_string(); + | ^^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:14:14 + | +LL | let _s = s.to_string(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:17:15 + | +LL | let _s = s.to_owned(); + | ^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:17:14 + | +LL | let _s = s.to_owned(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:19:42 + | +LL | let _s = Path::new("/a/b/").join("c").to_owned(); + | ^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:19:14 + | +LL | let _s = Path::new("/a/b/").join("c").to_owned(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:21:42 + | +LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); + | ^^^^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:21:14 + | +LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:23:29 + | +LL | let _s = OsString::new().to_owned(); + | ^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:23:14 + | +LL | let _s = OsString::new().to_owned(); + | ^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:25:29 + | +LL | let _s = OsString::new().to_os_string(); + | ^^^^^^^^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:25:14 + | +LL | let _s = OsString::new().to_os_string(); + | ^^^^^^^^^^^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:32:19 + | +LL | let _t = tup.0.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:32:14 + | +LL | let _t = tup.0.clone(); + | ^^^^^ + +error: redundant clone + --> $DIR/redundant_clone.rs:61:22 + | +LL | (a.clone(), a.clone()) + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:61:21 + | +LL | (a.clone(), a.clone()) + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:121:15 + | +LL | let _s = s.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:121:14 + | +LL | let _s = s.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:122:15 + | +LL | let _t = t.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:122:14 + | +LL | let _t = t.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:132:19 + | +LL | let _f = f.clone(); + | ^^^^^^^^ help: remove this + | +note: this value is dropped without further use + --> $DIR/redundant_clone.rs:132:18 + | +LL | let _f = f.clone(); + | ^ + +error: redundant clone + --> $DIR/redundant_clone.rs:144:14 + | +LL | let y = x.clone().join("matthias"); + | ^^^^^^^^ help: remove this + | +note: cloned value is neither consumed nor mutated + --> $DIR/redundant_clone.rs:144:13 + | +LL | let y = x.clone().join("matthias"); + | ^^^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_closure_call.rs b/src/tools/clippy/tests/ui/redundant_closure_call.rs new file mode 100644 index 0000000000000..bacd67db7c305 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call.rs @@ -0,0 +1,23 @@ +// non rustfixable, see redundant_closure_call_fixable.rs + +#![warn(clippy::redundant_closure_call)] + +fn main() { + let mut i = 1; + let mut k = (|m| m + 1)(i); + + k = (|a, b| a * b)(1, 5); + + let closure = || 32; + i = closure(); + + let closure = |i| i + 1; + i = closure(3); + + i = closure(4); + + #[allow(clippy::needless_return)] + (|| return 2)(); + (|| -> Option { None? })(); + (|| -> Result { Err(2)? })(); +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call.stderr b/src/tools/clippy/tests/ui/redundant_closure_call.stderr new file mode 100644 index 0000000000000..68c1416bb6b1a --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call.stderr @@ -0,0 +1,28 @@ +error: Closure called just once immediately after it was declared + --> $DIR/redundant_closure_call.rs:12:5 + | +LL | i = closure(); + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::redundant-closure-call` implied by `-D warnings` + +error: Closure called just once immediately after it was declared + --> $DIR/redundant_closure_call.rs:15:5 + | +LL | i = closure(3); + | ^^^^^^^^^^^^^^ + +error: Try not to call a closure in the expression where it is declared. + --> $DIR/redundant_closure_call.rs:7:17 + | +LL | let mut k = (|m| m + 1)(i); + | ^^^^^^^^^^^^^^ + +error: Try not to call a closure in the expression where it is declared. + --> $DIR/redundant_closure_call.rs:9:9 + | +LL | k = (|a, b| a * b)(1, 5); + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 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 new file mode 100644 index 0000000000000..0abca6fca0613 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.fixed @@ -0,0 +1,8 @@ +// run-rustfix + +#![warn(clippy::redundant_closure_call)] +#![allow(unused)] + +fn main() { + let a = 42; +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs new file mode 100644 index 0000000000000..f8b9d37a5cc4e --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.rs @@ -0,0 +1,8 @@ +// run-rustfix + +#![warn(clippy::redundant_closure_call)] +#![allow(unused)] + +fn main() { + let a = (|| 42)(); +} diff --git a/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr new file mode 100644 index 0000000000000..e7737f9dd856f --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_closure_call_fixable.stderr @@ -0,0 +1,10 @@ +error: Try not to call a closure in the expression where it is declared. + --> $DIR/redundant_closure_call_fixable.rs:7:13 + | +LL | let a = (|| 42)(); + | ^^^^^^^^^ help: Try doing something like: : `42` + | + = note: `-D clippy::redundant-closure-call` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/redundant_field_names.fixed b/src/tools/clippy/tests/ui/redundant_field_names.fixed new file mode 100644 index 0000000000000..5b4b8eeedd469 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_field_names.fixed @@ -0,0 +1,71 @@ +// run-rustfix +#![warn(clippy::redundant_field_names)] +#![allow(clippy::no_effect, dead_code, unused_variables)] + +#[macro_use] +extern crate derive_new; + +use std::ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; + +mod foo { + pub const BAR: u8 = 0; +} + +struct Person { + gender: u8, + age: u8, + name: u8, + buzz: u64, + foo: u8, +} + +#[derive(new)] +pub struct S { + v: String, +} + +fn main() { + let gender: u8 = 42; + let age = 0; + let fizz: u64 = 0; + let name: u8 = 0; + + let me = Person { + gender, + age, + + name, //should be ok + buzz: fizz, //should be ok + foo: foo::BAR, //should be ok + }; + + // Range expressions + let (start, end) = (0, 0); + + let _ = start..; + let _ = ..end; + let _ = start..end; + + let _ = ..=end; + let _ = start..=end; + + // Issue #2799 + let _: Vec<_> = (start..end).collect(); + + // hand-written Range family structs are linted + let _ = RangeFrom { start }; + let _ = RangeTo { end }; + let _ = Range { start, end }; + let _ = RangeInclusive::new(start, end); + let _ = RangeToInclusive { end }; +} + +fn issue_3476() { + fn foo() {} + + struct S { + foo: fn(), + } + + S { foo: foo:: }; +} diff --git a/src/tools/clippy/tests/ui/redundant_field_names.rs b/src/tools/clippy/tests/ui/redundant_field_names.rs new file mode 100644 index 0000000000000..3f97b80c56828 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_field_names.rs @@ -0,0 +1,71 @@ +// run-rustfix +#![warn(clippy::redundant_field_names)] +#![allow(clippy::no_effect, dead_code, unused_variables)] + +#[macro_use] +extern crate derive_new; + +use std::ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; + +mod foo { + pub const BAR: u8 = 0; +} + +struct Person { + gender: u8, + age: u8, + name: u8, + buzz: u64, + foo: u8, +} + +#[derive(new)] +pub struct S { + v: String, +} + +fn main() { + let gender: u8 = 42; + let age = 0; + let fizz: u64 = 0; + let name: u8 = 0; + + let me = Person { + gender: gender, + age: age, + + name, //should be ok + buzz: fizz, //should be ok + foo: foo::BAR, //should be ok + }; + + // Range expressions + let (start, end) = (0, 0); + + let _ = start..; + let _ = ..end; + let _ = start..end; + + let _ = ..=end; + let _ = start..=end; + + // Issue #2799 + let _: Vec<_> = (start..end).collect(); + + // hand-written Range family structs are linted + let _ = RangeFrom { start: start }; + let _ = RangeTo { end: end }; + let _ = Range { start: start, end: end }; + let _ = RangeInclusive::new(start, end); + let _ = RangeToInclusive { end: end }; +} + +fn issue_3476() { + fn foo() {} + + struct S { + foo: fn(), + } + + S { foo: foo:: }; +} diff --git a/src/tools/clippy/tests/ui/redundant_field_names.stderr b/src/tools/clippy/tests/ui/redundant_field_names.stderr new file mode 100644 index 0000000000000..7976292df2241 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_field_names.stderr @@ -0,0 +1,46 @@ +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:34:9 + | +LL | gender: gender, + | ^^^^^^^^^^^^^^ help: replace it with: `gender` + | + = note: `-D clippy::redundant-field-names` implied by `-D warnings` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:35:9 + | +LL | age: age, + | ^^^^^^^^ help: replace it with: `age` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:56:25 + | +LL | let _ = RangeFrom { start: start }; + | ^^^^^^^^^^^^ help: replace it with: `start` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:57:23 + | +LL | let _ = RangeTo { end: end }; + | ^^^^^^^^ help: replace it with: `end` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:58:21 + | +LL | let _ = Range { start: start, end: end }; + | ^^^^^^^^^^^^ help: replace it with: `start` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:58:35 + | +LL | let _ = Range { start: start, end: end }; + | ^^^^^^^^ help: replace it with: `end` + +error: redundant field names in struct initialization + --> $DIR/redundant_field_names.rs:60:32 + | +LL | let _ = RangeToInclusive { end: end }; + | ^^^^^^^^ help: replace it with: `end` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching.fixed b/src/tools/clippy/tests/ui/redundant_pattern_matching.fixed new file mode 100644 index 0000000000000..fc8cb0e747c73 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching.fixed @@ -0,0 +1,119 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(clippy::unit_arg, unused_must_use, clippy::needless_bool, deprecated)] + +fn main() { + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + if None::<()>.is_none() {} + + if Some(42).is_some() {} + + if Some(42).is_some() { + foo(); + } else { + bar(); + } + + while Some(42).is_some() {} + + while Some(42).is_none() {} + + while None::<()>.is_none() {} + + while Ok::(10).is_ok() {} + + while Ok::(10).is_err() {} + + let mut v = vec![1, 2, 3]; + while v.pop().is_some() { + foo(); + } + + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + if None::.is_none() {} + + if Some(42).is_some() {} + + if let Ok(x) = Ok::(42) { + println!("{}", x); + } + + Ok::(42).is_ok(); + + Ok::(42).is_err(); + + Err::(42).is_err(); + + Err::(42).is_ok(); + + Some(42).is_some(); + + None::<()>.is_none(); + + let _ = None::<()>.is_none(); + + let _ = if Ok::(4).is_ok() { true } else { false }; + + let opt = Some(false); + let x = if opt.is_some() { true } else { false }; + takes_bool(x); + + issue5504(); + + let _ = if gen_opt().is_some() { + 1 + } else if gen_opt().is_none() { + 2 + } else if gen_res().is_ok() { + 3 + } else if gen_res().is_err() { + 4 + } else { + 5 + }; +} + +fn gen_opt() -> Option<()> { + None +} + +fn gen_res() -> Result<(), ()> { + Ok(()) +} + +fn takes_bool(_: bool) {} + +fn foo() {} + +fn bar() {} + +macro_rules! m { + () => { + Some(42u32) + }; +} + +fn issue5504() { + fn result_opt() -> Result, i32> { + Err(42) + } + + fn try_result_opt() -> Result { + while r#try!(result_opt()).is_some() {} + if r#try!(result_opt()).is_some() {} + Ok(42) + } + + try_result_opt(); + + if m!().is_some() {} + while m!().is_some() {} +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching.rs b/src/tools/clippy/tests/ui/redundant_pattern_matching.rs new file mode 100644 index 0000000000000..51912dade0356 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching.rs @@ -0,0 +1,140 @@ +// run-rustfix + +#![warn(clippy::all)] +#![warn(clippy::redundant_pattern_matching)] +#![allow(clippy::unit_arg, unused_must_use, clippy::needless_bool, deprecated)] + +fn main() { + if let Ok(_) = Ok::(42) {} + + if let Err(_) = Err::(42) {} + + if let None = None::<()> {} + + if let Some(_) = Some(42) {} + + if let Some(_) = Some(42) { + foo(); + } else { + bar(); + } + + while let Some(_) = Some(42) {} + + while let None = Some(42) {} + + while let None = None::<()> {} + + while let Ok(_) = Ok::(10) {} + + while let Err(_) = Ok::(10) {} + + let mut v = vec![1, 2, 3]; + while let Some(_) = v.pop() { + foo(); + } + + if Ok::(42).is_ok() {} + + if Err::(42).is_err() {} + + if None::.is_none() {} + + if Some(42).is_some() {} + + if let Ok(x) = Ok::(42) { + println!("{}", x); + } + + match Ok::(42) { + Ok(_) => true, + Err(_) => false, + }; + + match Ok::(42) { + Ok(_) => false, + Err(_) => true, + }; + + match Err::(42) { + Ok(_) => false, + Err(_) => true, + }; + + match Err::(42) { + Ok(_) => true, + Err(_) => false, + }; + + match Some(42) { + Some(_) => true, + None => false, + }; + + match None::<()> { + Some(_) => false, + None => true, + }; + + let _ = match None::<()> { + Some(_) => false, + None => true, + }; + + let _ = if let Ok(_) = Ok::(4) { true } else { false }; + + let opt = Some(false); + let x = if let Some(_) = opt { true } else { false }; + takes_bool(x); + + issue5504(); + + let _ = if let Some(_) = gen_opt() { + 1 + } else if let None = gen_opt() { + 2 + } else if let Ok(_) = gen_res() { + 3 + } else if let Err(_) = gen_res() { + 4 + } else { + 5 + }; +} + +fn gen_opt() -> Option<()> { + None +} + +fn gen_res() -> Result<(), ()> { + Ok(()) +} + +fn takes_bool(_: bool) {} + +fn foo() {} + +fn bar() {} + +macro_rules! m { + () => { + Some(42u32) + }; +} + +fn issue5504() { + fn result_opt() -> Result, i32> { + Err(42) + } + + fn try_result_opt() -> Result { + while let Some(_) = r#try!(result_opt()) {} + if let Some(_) = r#try!(result_opt()) {} + Ok(42) + } + + try_result_opt(); + + if let Some(_) = m!() {} + while let Some(_) = m!() {} +} diff --git a/src/tools/clippy/tests/ui/redundant_pattern_matching.stderr b/src/tools/clippy/tests/ui/redundant_pattern_matching.stderr new file mode 100644 index 0000000000000..b58deb7954efe --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pattern_matching.stderr @@ -0,0 +1,194 @@ +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching.rs:8:12 + | +LL | if let Ok(_) = Ok::(42) {} + | -------^^^^^--------------------- help: try this: `if Ok::(42).is_ok()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching.rs:10:12 + | +LL | if let Err(_) = Err::(42) {} + | -------^^^^^^---------------------- help: try this: `if Err::(42).is_err()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching.rs:12:12 + | +LL | if let None = None::<()> {} + | -------^^^^------------- help: try this: `if None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:14:12 + | +LL | if let Some(_) = Some(42) {} + | -------^^^^^^^----------- help: try this: `if Some(42).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:16:12 + | +LL | if let Some(_) = Some(42) { + | -------^^^^^^^----------- help: try this: `if Some(42).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:22:15 + | +LL | while let Some(_) = Some(42) {} + | ----------^^^^^^^----------- help: try this: `while Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching.rs:24:15 + | +LL | while let None = Some(42) {} + | ----------^^^^----------- help: try this: `while Some(42).is_none()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching.rs:26:15 + | +LL | while let None = None::<()> {} + | ----------^^^^------------- help: try this: `while None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching.rs:28:15 + | +LL | while let Ok(_) = Ok::(10) {} + | ----------^^^^^--------------------- help: try this: `while Ok::(10).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching.rs:30:15 + | +LL | while let Err(_) = Ok::(10) {} + | ----------^^^^^^--------------------- help: try this: `while Ok::(10).is_err()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:33:15 + | +LL | while let Some(_) = v.pop() { + | ----------^^^^^^^---------- help: try this: `while v.pop().is_some()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching.rs:49:5 + | +LL | / match Ok::(42) { +LL | | Ok(_) => true, +LL | | Err(_) => false, +LL | | }; + | |_____^ help: try this: `Ok::(42).is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching.rs:54:5 + | +LL | / match Ok::(42) { +LL | | Ok(_) => false, +LL | | Err(_) => true, +LL | | }; + | |_____^ help: try this: `Ok::(42).is_err()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching.rs:59:5 + | +LL | / match Err::(42) { +LL | | Ok(_) => false, +LL | | Err(_) => true, +LL | | }; + | |_____^ help: try this: `Err::(42).is_err()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching.rs:64:5 + | +LL | / match Err::(42) { +LL | | Ok(_) => true, +LL | | Err(_) => false, +LL | | }; + | |_____^ help: try this: `Err::(42).is_ok()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:69:5 + | +LL | / match Some(42) { +LL | | Some(_) => true, +LL | | None => false, +LL | | }; + | |_____^ help: try this: `Some(42).is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching.rs:74:5 + | +LL | / match None::<()> { +LL | | Some(_) => false, +LL | | None => true, +LL | | }; + | |_____^ help: try this: `None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching.rs:79:13 + | +LL | let _ = match None::<()> { + | _____________^ +LL | | Some(_) => false, +LL | | None => true, +LL | | }; + | |_____^ help: try this: `None::<()>.is_none()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching.rs:84:20 + | +LL | let _ = if let Ok(_) = Ok::(4) { true } else { false }; + | -------^^^^^--------------------- help: try this: `if Ok::(4).is_ok()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:87:20 + | +LL | let x = if let Some(_) = opt { true } else { false }; + | -------^^^^^^^------ help: try this: `if opt.is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:92:20 + | +LL | let _ = if let Some(_) = gen_opt() { + | -------^^^^^^^------------ help: try this: `if gen_opt().is_some()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching.rs:94:19 + | +LL | } else if let None = gen_opt() { + | -------^^^^------------ help: try this: `if gen_opt().is_none()` + +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/redundant_pattern_matching.rs:96:19 + | +LL | } else if let Ok(_) = gen_res() { + | -------^^^^^------------ help: try this: `if gen_res().is_ok()` + +error: redundant pattern matching, consider using `is_err()` + --> $DIR/redundant_pattern_matching.rs:98:19 + | +LL | } else if let Err(_) = gen_res() { + | -------^^^^^^------------ help: try this: `if gen_res().is_err()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:131:19 + | +LL | while let Some(_) = r#try!(result_opt()) {} + | ----------^^^^^^^----------------------- help: try this: `while r#try!(result_opt()).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:132:16 + | +LL | if let Some(_) = r#try!(result_opt()) {} + | -------^^^^^^^----------------------- help: try this: `if r#try!(result_opt()).is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:138:12 + | +LL | if let Some(_) = m!() {} + | -------^^^^^^^------- help: try this: `if m!().is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> $DIR/redundant_pattern_matching.rs:139:15 + | +LL | while let Some(_) = m!() {} + | ----------^^^^^^^------- help: try this: `while m!().is_some()` + +error: aborting due to 28 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_pub_crate.fixed b/src/tools/clippy/tests/ui/redundant_pub_crate.fixed new file mode 100644 index 0000000000000..25f2fd061b88e --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pub_crate.fixed @@ -0,0 +1,107 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::redundant_pub_crate)] + +mod m1 { + fn f() {} + pub fn g() {} // private due to m1 + pub fn h() {} + + mod m1_1 { + fn f() {} + pub fn g() {} // private due to m1_1 and m1 + pub fn h() {} + } + + pub mod m1_2 { + // ^ private due to m1 + fn f() {} + pub fn g() {} // private due to m1_2 and m1 + pub fn h() {} + } + + pub mod m1_3 { + fn f() {} + pub fn g() {} // private due to m1 + pub fn h() {} + } +} + +pub(crate) mod m2 { + fn f() {} + pub fn g() {} // already crate visible due to m2 + pub fn h() {} + + mod m2_1 { + fn f() {} + pub fn g() {} // private due to m2_1 + pub fn h() {} + } + + pub mod m2_2 { + // ^ already crate visible due to m2 + fn f() {} + pub fn g() {} // already crate visible due to m2_2 and m2 + pub fn h() {} + } + + pub mod m2_3 { + fn f() {} + pub fn g() {} // already crate visible due to m2 + pub fn h() {} + } +} + +pub mod m3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 is exported + pub fn h() {} + + mod m3_1 { + fn f() {} + pub fn g() {} // private due to m3_1 + pub fn h() {} + } + + pub(crate) mod m3_2 { + // ^ ok + fn f() {} + pub fn g() {} // already crate visible due to m3_2 + pub fn h() {} + } + + pub mod m3_3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 and m3_3 are exported + pub fn h() {} + } +} + +mod m4 { + fn f() {} + pub fn g() {} // private: not re-exported by `pub use m4::*` + pub fn h() {} + + mod m4_1 { + fn f() {} + pub fn g() {} // private due to m4_1 + pub fn h() {} + } + + pub mod m4_2 { + // ^ private: not re-exported by `pub use m4::*` + fn f() {} + pub fn g() {} // private due to m4_2 + pub fn h() {} + } + + pub mod m4_3 { + fn f() {} + pub(crate) fn g() {} // ok: m4_3 is re-exported by `pub use m4::*` + pub fn h() {} + } +} + +pub use m4::*; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_pub_crate.rs b/src/tools/clippy/tests/ui/redundant_pub_crate.rs new file mode 100644 index 0000000000000..616286b4f39f4 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pub_crate.rs @@ -0,0 +1,107 @@ +// run-rustfix +#![allow(dead_code)] +#![warn(clippy::redundant_pub_crate)] + +mod m1 { + fn f() {} + pub(crate) fn g() {} // private due to m1 + pub fn h() {} + + mod m1_1 { + fn f() {} + pub(crate) fn g() {} // private due to m1_1 and m1 + pub fn h() {} + } + + pub(crate) mod m1_2 { + // ^ private due to m1 + fn f() {} + pub(crate) fn g() {} // private due to m1_2 and m1 + pub fn h() {} + } + + pub mod m1_3 { + fn f() {} + pub(crate) fn g() {} // private due to m1 + pub fn h() {} + } +} + +pub(crate) mod m2 { + fn f() {} + pub(crate) fn g() {} // already crate visible due to m2 + pub fn h() {} + + mod m2_1 { + fn f() {} + pub(crate) fn g() {} // private due to m2_1 + pub fn h() {} + } + + pub(crate) mod m2_2 { + // ^ already crate visible due to m2 + fn f() {} + pub(crate) fn g() {} // already crate visible due to m2_2 and m2 + pub fn h() {} + } + + pub mod m2_3 { + fn f() {} + pub(crate) fn g() {} // already crate visible due to m2 + pub fn h() {} + } +} + +pub mod m3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 is exported + pub fn h() {} + + mod m3_1 { + fn f() {} + pub(crate) fn g() {} // private due to m3_1 + pub fn h() {} + } + + pub(crate) mod m3_2 { + // ^ ok + fn f() {} + pub(crate) fn g() {} // already crate visible due to m3_2 + pub fn h() {} + } + + pub mod m3_3 { + fn f() {} + pub(crate) fn g() {} // ok: m3 and m3_3 are exported + pub fn h() {} + } +} + +mod m4 { + fn f() {} + pub(crate) fn g() {} // private: not re-exported by `pub use m4::*` + pub fn h() {} + + mod m4_1 { + fn f() {} + pub(crate) fn g() {} // private due to m4_1 + pub fn h() {} + } + + pub(crate) mod m4_2 { + // ^ private: not re-exported by `pub use m4::*` + fn f() {} + pub(crate) fn g() {} // private due to m4_2 + pub fn h() {} + } + + pub mod m4_3 { + fn f() {} + pub(crate) fn g() {} // ok: m4_3 is re-exported by `pub use m4::*` + pub fn h() {} + } +} + +pub use m4::*; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_pub_crate.stderr b/src/tools/clippy/tests/ui/redundant_pub_crate.stderr new file mode 100644 index 0000000000000..6fccdaa4e2037 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_pub_crate.stderr @@ -0,0 +1,132 @@ +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:7:5 + | +LL | pub(crate) fn g() {} // private due to m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + | + = note: `-D clippy::redundant-pub-crate` implied by `-D warnings` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:12:9 + | +LL | pub(crate) fn g() {} // private due to m1_1 and m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) module inside private module + --> $DIR/redundant_pub_crate.rs:16:5 + | +LL | pub(crate) mod m1_2 { + | ----------^^^^^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:19:9 + | +LL | pub(crate) fn g() {} // private due to m1_2 and m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:25:9 + | +LL | pub(crate) fn g() {} // private due to m1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:32:5 + | +LL | pub(crate) fn g() {} // already crate visible due to m2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:37:9 + | +LL | pub(crate) fn g() {} // private due to m2_1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) module inside private module + --> $DIR/redundant_pub_crate.rs:41:5 + | +LL | pub(crate) mod m2_2 { + | ----------^^^^^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:44:9 + | +LL | pub(crate) fn g() {} // already crate visible due to m2_2 and m2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:50:9 + | +LL | pub(crate) fn g() {} // already crate visible due to m2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:62:9 + | +LL | pub(crate) fn g() {} // private due to m3_1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:69:9 + | +LL | pub(crate) fn g() {} // already crate visible due to m3_2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:82:5 + | +LL | pub(crate) fn g() {} // private: not re-exported by `pub use m4::*` + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:87:9 + | +LL | pub(crate) fn g() {} // private due to m4_1 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) module inside private module + --> $DIR/redundant_pub_crate.rs:91:5 + | +LL | pub(crate) mod m4_2 { + | ----------^^^^^^^^^ + | | + | help: consider using: `pub` + +error: pub(crate) function inside private module + --> $DIR/redundant_pub_crate.rs:94:9 + | +LL | pub(crate) fn g() {} // private due to m4_2 + | ----------^^^^^ + | | + | help: consider using: `pub` + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes.fixed b/src/tools/clippy/tests/ui/redundant_static_lifetimes.fixed new file mode 100644 index 0000000000000..921249606ad27 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes.fixed @@ -0,0 +1,56 @@ +// run-rustfix + +#![allow(unused)] + +#[derive(Debug)] +struct Foo {} + +const VAR_ONE: &str = "Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TWO: &str = "Test constant #2"; // This line should not raise a warning. + +const VAR_THREE: &[&str] = &["one", "two"]; // ERROR Consider removing 'static + +const VAR_FOUR: (&str, (&str, &str), &str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + +const VAR_SIX: &u8 = &5; + +const VAR_HEIGHT: &Foo = &Foo {}; + +const VAR_SLICE: &[u8] = b"Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TUPLE: &(u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +const VAR_ARRAY: &[u8; 1] = b"T"; // ERROR Consider removing 'static. + +static STATIC_VAR_ONE: &str = "Test static #1"; // ERROR Consider removing 'static. + +static STATIC_VAR_TWO: &str = "Test static #2"; // This line should not raise a warning. + +static STATIC_VAR_THREE: &[&str] = &["one", "two"]; // ERROR Consider removing 'static + +static STATIC_VAR_SIX: &u8 = &5; + +static STATIC_VAR_HEIGHT: &Foo = &Foo {}; + +static STATIC_VAR_SLICE: &[u8] = b"Test static #3"; // ERROR Consider removing 'static. + +static STATIC_VAR_TUPLE: &(u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +static STATIC_VAR_ARRAY: &[u8; 1] = b"T"; // ERROR Consider removing 'static. + +fn main() { + let false_positive: &'static str = "test"; +} + +trait Bar { + const TRAIT_VAR: &'static str; +} + +impl Foo { + const IMPL_VAR: &'static str = "var"; +} + +impl Bar for Foo { + const TRAIT_VAR: &'static str = "foo"; +} diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes.rs b/src/tools/clippy/tests/ui/redundant_static_lifetimes.rs new file mode 100644 index 0000000000000..4d4b249d076ff --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes.rs @@ -0,0 +1,56 @@ +// run-rustfix + +#![allow(unused)] + +#[derive(Debug)] +struct Foo {} + +const VAR_ONE: &'static str = "Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TWO: &str = "Test constant #2"; // This line should not raise a warning. + +const VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + +const VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + +const VAR_SIX: &'static u8 = &5; + +const VAR_HEIGHT: &'static Foo = &Foo {}; + +const VAR_SLICE: &'static [u8] = b"Test constant #1"; // ERROR Consider removing 'static. + +const VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +const VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + +static STATIC_VAR_ONE: &'static str = "Test static #1"; // ERROR Consider removing 'static. + +static STATIC_VAR_TWO: &str = "Test static #2"; // This line should not raise a warning. + +static STATIC_VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + +static STATIC_VAR_SIX: &'static u8 = &5; + +static STATIC_VAR_HEIGHT: &'static Foo = &Foo {}; + +static STATIC_VAR_SLICE: &'static [u8] = b"Test static #3"; // ERROR Consider removing 'static. + +static STATIC_VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + +static STATIC_VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + +fn main() { + let false_positive: &'static str = "test"; +} + +trait Bar { + const TRAIT_VAR: &'static str; +} + +impl Foo { + const IMPL_VAR: &'static str = "var"; +} + +impl Bar for Foo { + const TRAIT_VAR: &'static str = "foo"; +} diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes.stderr b/src/tools/clippy/tests/ui/redundant_static_lifetimes.stderr new file mode 100644 index 0000000000000..3c3d2eacd8d9c --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes.stderr @@ -0,0 +1,100 @@ +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:8:17 + | +LL | const VAR_ONE: &'static str = "Test constant #1"; // ERROR Consider removing 'static. + | -^^^^^^^---- help: consider removing `'static`: `&str` + | + = note: `-D clippy::redundant-static-lifetimes` implied by `-D warnings` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:12:21 + | +LL | const VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:14:32 + | +LL | const VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:14:47 + | +LL | const VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:16:17 + | +LL | const VAR_SIX: &'static u8 = &5; + | -^^^^^^^--- help: consider removing `'static`: `&u8` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:18:20 + | +LL | const VAR_HEIGHT: &'static Foo = &Foo {}; + | -^^^^^^^---- help: consider removing `'static`: `&Foo` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:20:19 + | +LL | const VAR_SLICE: &'static [u8] = b"Test constant #1"; // ERROR Consider removing 'static. + | -^^^^^^^----- help: consider removing `'static`: `&[u8]` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:22:19 + | +LL | const VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + | -^^^^^^^--------- help: consider removing `'static`: `&(u8, u8)` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:24:19 + | +LL | const VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + | -^^^^^^^-------- help: consider removing `'static`: `&[u8; 1]` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:26:25 + | +LL | static STATIC_VAR_ONE: &'static str = "Test static #1"; // ERROR Consider removing 'static. + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:30:29 + | +LL | static STATIC_VAR_THREE: &[&'static str] = &["one", "two"]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:32:25 + | +LL | static STATIC_VAR_SIX: &'static u8 = &5; + | -^^^^^^^--- help: consider removing `'static`: `&u8` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:34:28 + | +LL | static STATIC_VAR_HEIGHT: &'static Foo = &Foo {}; + | -^^^^^^^---- help: consider removing `'static`: `&Foo` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:36:27 + | +LL | static STATIC_VAR_SLICE: &'static [u8] = b"Test static #3"; // ERROR Consider removing 'static. + | -^^^^^^^----- help: consider removing `'static`: `&[u8]` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:38:27 + | +LL | static STATIC_VAR_TUPLE: &'static (u8, u8) = &(1, 2); // ERROR Consider removing 'static. + | -^^^^^^^--------- help: consider removing `'static`: `&(u8, u8)` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes.rs:40:27 + | +LL | static STATIC_VAR_ARRAY: &'static [u8; 1] = b"T"; // ERROR Consider removing 'static. + | -^^^^^^^-------- help: consider removing `'static`: `&[u8; 1]` + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.rs b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.rs new file mode 100644 index 0000000000000..f57dd58e230a3 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.rs @@ -0,0 +1,13 @@ +// these are rustfixable, but run-rustfix tests cannot handle them + +const VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + +const VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + +static STATIC_VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + +static STATIC_VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + +static STATIC_VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.stderr b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.stderr new file mode 100644 index 0000000000000..afc853dcfce83 --- /dev/null +++ b/src/tools/clippy/tests/ui/redundant_static_lifetimes_multiple.stderr @@ -0,0 +1,64 @@ +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:3:18 + | +LL | const VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^------------------ help: consider removing `'static`: `&[&[&'static str]]` + | + = note: `-D clippy::redundant-static-lifetimes` implied by `-D warnings` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:3:30 + | +LL | const VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:5:29 + | +LL | const VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^--------------- help: consider removing `'static`: `&[&'static str]` + +error: Constants have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:5:39 + | +LL | const VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:7:40 + | +LL | static STATIC_VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:7:55 + | +LL | static STATIC_VAR_FOUR: (&str, (&str, &'static str), &'static str) = ("on", ("th", "th"), "on"); // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:9:26 + | +LL | static STATIC_VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^------------------ help: consider removing `'static`: `&[&[&'static str]]` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:9:38 + | +LL | static STATIC_VAR_FIVE: &'static [&[&'static str]] = &[&["test"], &["other one"]]; // ERROR Consider removing 'static + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:11:37 + | +LL | static STATIC_VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^--------------- help: consider removing `'static`: `&[&'static str]` + +error: Statics have by default a `'static` lifetime + --> $DIR/redundant_static_lifetimes_multiple.rs:11:47 + | +LL | static STATIC_VAR_SEVEN: &[&(&str, &'static [&'static str])] = &[&("one", &["other one"])]; + | -^^^^^^^---- help: consider removing `'static`: `&str` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/regex.rs b/src/tools/clippy/tests/ui/regex.rs new file mode 100644 index 0000000000000..b523fa5b711ae --- /dev/null +++ b/src/tools/clippy/tests/ui/regex.rs @@ -0,0 +1,79 @@ +#![allow(unused)] +#![warn(clippy::invalid_regex, clippy::trivial_regex, clippy::regex_macro)] + +extern crate regex; + +use regex::bytes::{Regex as BRegex, RegexBuilder as BRegexBuilder, RegexSet as BRegexSet}; +use regex::{Regex, RegexBuilder, RegexSet}; + +const OPENING_PAREN: &str = "("; +const NOT_A_REAL_REGEX: &str = "foobar"; + +fn syntax_error() { + let pipe_in_wrong_position = Regex::new("|"); + let pipe_in_wrong_position_builder = RegexBuilder::new("|"); + let wrong_char_ranice = Regex::new("[z-a]"); + let some_unicode = Regex::new("[é-è]"); + + let some_regex = Regex::new(OPENING_PAREN); + + let binary_pipe_in_wrong_position = BRegex::new("|"); + let some_binary_regex = BRegex::new(OPENING_PAREN); + let some_binary_regex_builder = BRegexBuilder::new(OPENING_PAREN); + + let closing_paren = ")"; + let not_linted = Regex::new(closing_paren); + + let set = RegexSet::new(&[r"[a-z]+@[a-z]+\.(com|org|net)", r"[a-z]+\.(com|org|net)"]); + let bset = BRegexSet::new(&[ + r"[a-z]+@[a-z]+\.(com|org|net)", + r"[a-z]+\.(com|org|net)", + r".", // regression test + ]); + + let set_error = RegexSet::new(&[OPENING_PAREN, r"[a-z]+\.(com|org|net)"]); + let bset_error = BRegexSet::new(&[OPENING_PAREN, r"[a-z]+\.(com|org|net)"]); + + let raw_string_error = Regex::new(r"[...\/...]"); + let raw_string_error = Regex::new(r#"[...\/...]"#); +} + +fn trivial_regex() { + let trivial_eq = Regex::new("^foobar$"); + + let trivial_eq_builder = RegexBuilder::new("^foobar$"); + + let trivial_starts_with = Regex::new("^foobar"); + + let trivial_ends_with = Regex::new("foobar$"); + + let trivial_contains = Regex::new("foobar"); + + let trivial_contains = Regex::new(NOT_A_REAL_REGEX); + + let trivial_backslash = Regex::new("a\\.b"); + + // unlikely corner cases + let trivial_empty = Regex::new(""); + + let trivial_empty = Regex::new("^"); + + let trivial_empty = Regex::new("^$"); + + let binary_trivial_empty = BRegex::new("^$"); + + // non-trivial regexes + let non_trivial_dot = Regex::new("a.b"); + let non_trivial_dot_builder = RegexBuilder::new("a.b"); + let non_trivial_eq = Regex::new("^foo|bar$"); + let non_trivial_starts_with = Regex::new("^foo|bar"); + let non_trivial_ends_with = Regex::new("^foo|bar"); + let non_trivial_ends_with = Regex::new("foo|bar"); + let non_trivial_binary = BRegex::new("foo|bar"); + let non_trivial_binary_builder = BRegexBuilder::new("foo|bar"); +} + +fn main() { + syntax_error(); + trivial_regex(); +} diff --git a/src/tools/clippy/tests/ui/regex.stderr b/src/tools/clippy/tests/ui/regex.stderr new file mode 100644 index 0000000000000..1394a9b63bc61 --- /dev/null +++ b/src/tools/clippy/tests/ui/regex.stderr @@ -0,0 +1,171 @@ +error: trivial regex + --> $DIR/regex.rs:13:45 + | +LL | let pipe_in_wrong_position = Regex::new("|"); + | ^^^ + | + = note: `-D clippy::trivial-regex` implied by `-D warnings` + = help: the regex is unlikely to be useful as it is + +error: trivial regex + --> $DIR/regex.rs:14:60 + | +LL | let pipe_in_wrong_position_builder = RegexBuilder::new("|"); + | ^^^ + | + = help: the regex is unlikely to be useful as it is + +error: regex syntax error: invalid character class range, the start must be <= the end + --> $DIR/regex.rs:15:42 + | +LL | let wrong_char_ranice = Regex::new("[z-a]"); + | ^^^ + | + = note: `-D clippy::invalid-regex` implied by `-D warnings` + +error: regex syntax error: invalid character class range, the start must be <= the end + --> $DIR/regex.rs:16:37 + | +LL | let some_unicode = Regex::new("[é-è]"); + | ^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:18:33 + | +LL | let some_regex = Regex::new(OPENING_PAREN); + | ^^^^^^^^^^^^^ + +error: trivial regex + --> $DIR/regex.rs:20:53 + | +LL | let binary_pipe_in_wrong_position = BRegex::new("|"); + | ^^^ + | + = help: the regex is unlikely to be useful as it is + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:21:41 + | +LL | let some_binary_regex = BRegex::new(OPENING_PAREN); + | ^^^^^^^^^^^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:22:56 + | +LL | let some_binary_regex_builder = BRegexBuilder::new(OPENING_PAREN); + | ^^^^^^^^^^^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:34:37 + | +LL | let set_error = RegexSet::new(&[OPENING_PAREN, r"[a-z]+/.(com|org|net)"]); + | ^^^^^^^^^^^^^ + +error: regex syntax error on position 0: unclosed group + --> $DIR/regex.rs:35:39 + | +LL | let bset_error = BRegexSet::new(&[OPENING_PAREN, r"[a-z]+/.(com|org|net)"]); + | ^^^^^^^^^^^^^ + +error: regex syntax error: unrecognized escape sequence + --> $DIR/regex.rs:37:45 + | +LL | let raw_string_error = Regex::new(r"[...//...]"); + | ^^ + +error: regex syntax error: unrecognized escape sequence + --> $DIR/regex.rs:38:46 + | +LL | let raw_string_error = Regex::new(r#"[...//...]"#); + | ^^ + +error: trivial regex + --> $DIR/regex.rs:42:33 + | +LL | let trivial_eq = Regex::new("^foobar$"); + | ^^^^^^^^^^ + | + = help: consider using `==` on `str`s + +error: trivial regex + --> $DIR/regex.rs:44:48 + | +LL | let trivial_eq_builder = RegexBuilder::new("^foobar$"); + | ^^^^^^^^^^ + | + = help: consider using `==` on `str`s + +error: trivial regex + --> $DIR/regex.rs:46:42 + | +LL | let trivial_starts_with = Regex::new("^foobar"); + | ^^^^^^^^^ + | + = help: consider using `str::starts_with` + +error: trivial regex + --> $DIR/regex.rs:48:40 + | +LL | let trivial_ends_with = Regex::new("foobar$"); + | ^^^^^^^^^ + | + = help: consider using `str::ends_with` + +error: trivial regex + --> $DIR/regex.rs:50:39 + | +LL | let trivial_contains = Regex::new("foobar"); + | ^^^^^^^^ + | + = help: consider using `str::contains` + +error: trivial regex + --> $DIR/regex.rs:52:39 + | +LL | let trivial_contains = Regex::new(NOT_A_REAL_REGEX); + | ^^^^^^^^^^^^^^^^ + | + = help: consider using `str::contains` + +error: trivial regex + --> $DIR/regex.rs:54:40 + | +LL | let trivial_backslash = Regex::new("a/.b"); + | ^^^^^^^ + | + = help: consider using `str::contains` + +error: trivial regex + --> $DIR/regex.rs:57:36 + | +LL | let trivial_empty = Regex::new(""); + | ^^ + | + = help: the regex is unlikely to be useful as it is + +error: trivial regex + --> $DIR/regex.rs:59:36 + | +LL | let trivial_empty = Regex::new("^"); + | ^^^ + | + = help: the regex is unlikely to be useful as it is + +error: trivial regex + --> $DIR/regex.rs:61:36 + | +LL | let trivial_empty = Regex::new("^$"); + | ^^^^ + | + = help: consider using `str::is_empty` + +error: trivial regex + --> $DIR/regex.rs:63:44 + | +LL | let binary_trivial_empty = BRegex::new("^$"); + | ^^^^ + | + = help: consider using `str::is_empty` + +error: aborting due to 23 previous errors + diff --git a/src/tools/clippy/tests/ui/rename.fixed b/src/tools/clippy/tests/ui/rename.fixed new file mode 100644 index 0000000000000..13fbb6e2a6eed --- /dev/null +++ b/src/tools/clippy/tests/ui/rename.fixed @@ -0,0 +1,19 @@ +//! Test for Clippy lint renames. +// run-rustfix + +#![allow(dead_code)] +// allow the new lint name here, to test if the new name works +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_static_lifetimes)] +// warn for the old lint name here, to test if the renaming worked +#![warn(clippy::cognitive_complexity)] + +#[warn(clippy::module_name_repetitions)] +fn main() {} + +#[warn(clippy::new_without_default)] +struct Foo; + +#[warn(clippy::redundant_static_lifetimes)] +fn foo() {} diff --git a/src/tools/clippy/tests/ui/rename.rs b/src/tools/clippy/tests/ui/rename.rs new file mode 100644 index 0000000000000..cbd3b1e91666a --- /dev/null +++ b/src/tools/clippy/tests/ui/rename.rs @@ -0,0 +1,19 @@ +//! Test for Clippy lint renames. +// run-rustfix + +#![allow(dead_code)] +// allow the new lint name here, to test if the new name works +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_static_lifetimes)] +// warn for the old lint name here, to test if the renaming worked +#![warn(clippy::cyclomatic_complexity)] + +#[warn(clippy::stutter)] +fn main() {} + +#[warn(clippy::new_without_default_derive)] +struct Foo; + +#[warn(clippy::const_static_lifetime)] +fn foo() {} diff --git a/src/tools/clippy/tests/ui/rename.stderr b/src/tools/clippy/tests/ui/rename.stderr new file mode 100644 index 0000000000000..a9e803946041e --- /dev/null +++ b/src/tools/clippy/tests/ui/rename.stderr @@ -0,0 +1,34 @@ +error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` + --> $DIR/rename.rs:10:9 + | +LL | #![warn(clippy::cyclomatic_complexity)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + +error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` + --> $DIR/rename.rs:12:8 + | +LL | #[warn(clippy::stutter)] + | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` + +error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` + --> $DIR/rename.rs:15:8 + | +LL | #[warn(clippy::new_without_default_derive)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` + +error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` + --> $DIR/rename.rs:18:8 + | +LL | #[warn(clippy::const_static_lifetime)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` + +error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` + --> $DIR/rename.rs:10:9 + | +LL | #![warn(clippy::cyclomatic_complexity)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/renamed_builtin_attr.fixed b/src/tools/clippy/tests/ui/renamed_builtin_attr.fixed new file mode 100644 index 0000000000000..cb91b841d2cb2 --- /dev/null +++ b/src/tools/clippy/tests/ui/renamed_builtin_attr.fixed @@ -0,0 +1,4 @@ +// run-rustfix + +#[clippy::cognitive_complexity = "1"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/renamed_builtin_attr.rs b/src/tools/clippy/tests/ui/renamed_builtin_attr.rs new file mode 100644 index 0000000000000..b3ce2758067cf --- /dev/null +++ b/src/tools/clippy/tests/ui/renamed_builtin_attr.rs @@ -0,0 +1,4 @@ +// run-rustfix + +#[clippy::cyclomatic_complexity = "1"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/renamed_builtin_attr.stderr b/src/tools/clippy/tests/ui/renamed_builtin_attr.stderr new file mode 100644 index 0000000000000..a399ff52fb8b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/renamed_builtin_attr.stderr @@ -0,0 +1,8 @@ +error: Usage of deprecated attribute + --> $DIR/renamed_builtin_attr.rs:3:11 + | +LL | #[clippy::cyclomatic_complexity = "1"] + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `cognitive_complexity` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/repl_uninit.rs b/src/tools/clippy/tests/ui/repl_uninit.rs new file mode 100644 index 0000000000000..346972b7bb4e0 --- /dev/null +++ b/src/tools/clippy/tests/ui/repl_uninit.rs @@ -0,0 +1,35 @@ +#![allow(deprecated, invalid_value)] +#![warn(clippy::all)] + +use std::mem; + +fn might_panic(x: X) -> X { + // in practice this would be a possibly-panicky operation + x +} + +fn main() { + let mut v = vec![0i32; 4]; + // the following is UB if `might_panic` panics + unsafe { + let taken_v = mem::replace(&mut v, mem::uninitialized()); + let new_v = might_panic(taken_v); + std::mem::forget(mem::replace(&mut v, new_v)); + } + + unsafe { + let taken_v = mem::replace(&mut v, mem::zeroed()); + let new_v = might_panic(taken_v); + std::mem::forget(mem::replace(&mut v, new_v)); + } + + // this is silly but OK, because usize is a primitive type + let mut u: usize = 42; + let uref = &mut u; + let taken_u = unsafe { mem::replace(uref, mem::zeroed()) }; + *uref = taken_u + 1; + + // this is still not OK, because uninit + let taken_u = unsafe { mem::replace(uref, mem::uninitialized()) }; + *uref = taken_u + 1; +} diff --git a/src/tools/clippy/tests/ui/repl_uninit.stderr b/src/tools/clippy/tests/ui/repl_uninit.stderr new file mode 100644 index 0000000000000..c1f55d7601e5c --- /dev/null +++ b/src/tools/clippy/tests/ui/repl_uninit.stderr @@ -0,0 +1,27 @@ +error: replacing with `mem::uninitialized()` + --> $DIR/repl_uninit.rs:15:23 + | +LL | let taken_v = mem::replace(&mut v, mem::uninitialized()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::mem-replace-with-uninit` implied by `-D warnings` + = help: consider using the `take_mut` crate instead + +error: replacing with `mem::zeroed()` + --> $DIR/repl_uninit.rs:21:23 + | +LL | let taken_v = mem::replace(&mut v, mem::zeroed()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using a default value or the `take_mut` crate instead + +error: replacing with `mem::uninitialized()` + --> $DIR/repl_uninit.rs:33:28 + | +LL | let taken_u = unsafe { mem::replace(uref, mem::uninitialized()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `take_mut` crate instead + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.rs b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.rs new file mode 100644 index 0000000000000..38fc9969804fa --- /dev/null +++ b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.rs @@ -0,0 +1,42 @@ +#![warn(clippy::rest_pat_in_fully_bound_structs)] + +struct A { + a: i32, + b: i64, + c: &'static str, +} + +macro_rules! foo { + ($param:expr) => { + match $param { + A { a: 0, b: 0, c: "", .. } => {}, + _ => {}, + } + }; +} + +fn main() { + let a_struct = A { a: 5, b: 42, c: "A" }; + + match a_struct { + A { a: 5, b: 42, c: "", .. } => {}, // Lint + A { a: 0, b: 0, c: "", .. } => {}, // Lint + _ => {}, + } + + match a_struct { + A { a: 5, b: 42, .. } => {}, + A { a: 0, b: 0, c: "", .. } => {}, // Lint + _ => {}, + } + + // No lint + match a_struct { + A { a: 5, .. } => {}, + A { a: 0, b: 0, .. } => {}, + _ => {}, + } + + // No lint + foo!(a_struct); +} diff --git a/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.stderr b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.stderr new file mode 100644 index 0000000000000..57ebd47f8c7ac --- /dev/null +++ b/src/tools/clippy/tests/ui/rest_pat_in_fully_bound_structs.stderr @@ -0,0 +1,27 @@ +error: unnecessary use of `..` pattern in struct binding. All fields were already bound + --> $DIR/rest_pat_in_fully_bound_structs.rs:22:9 + | +LL | A { a: 5, b: 42, c: "", .. } => {}, // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::rest-pat-in-fully-bound-structs` implied by `-D warnings` + = help: consider removing `..` from this binding + +error: unnecessary use of `..` pattern in struct binding. All fields were already bound + --> $DIR/rest_pat_in_fully_bound_structs.rs:23:9 + | +LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `..` from this binding + +error: unnecessary use of `..` pattern in struct binding. All fields were already bound + --> $DIR/rest_pat_in_fully_bound_structs.rs:29:9 + | +LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `..` from this binding + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/result_map_or_into_option.fixed b/src/tools/clippy/tests/ui/result_map_or_into_option.fixed new file mode 100644 index 0000000000000..331531b5165f6 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_or_into_option.fixed @@ -0,0 +1,19 @@ +// run-rustfix + +#![warn(clippy::result_map_or_into_option)] + +fn main() { + let opt: Result = Ok(1); + let _ = opt.ok(); + + let rewrap = |s: u32| -> Option { Some(s) }; + + // A non-Some `f` arg should not emit the lint + let opt: Result = Ok(1); + let _ = opt.map_or(None, rewrap); + + // A non-Some `f` closure where the argument is not used as the + // return should not emit the lint + let opt: Result = Ok(1); + opt.map_or(None, |_x| Some(1)); +} diff --git a/src/tools/clippy/tests/ui/result_map_or_into_option.rs b/src/tools/clippy/tests/ui/result_map_or_into_option.rs new file mode 100644 index 0000000000000..3058480e2ad3d --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_or_into_option.rs @@ -0,0 +1,19 @@ +// run-rustfix + +#![warn(clippy::result_map_or_into_option)] + +fn main() { + let opt: Result = Ok(1); + let _ = opt.map_or(None, Some); + + let rewrap = |s: u32| -> Option { Some(s) }; + + // A non-Some `f` arg should not emit the lint + let opt: Result = Ok(1); + let _ = opt.map_or(None, rewrap); + + // A non-Some `f` closure where the argument is not used as the + // return should not emit the lint + let opt: Result = Ok(1); + opt.map_or(None, |_x| Some(1)); +} diff --git a/src/tools/clippy/tests/ui/result_map_or_into_option.stderr b/src/tools/clippy/tests/ui/result_map_or_into_option.stderr new file mode 100644 index 0000000000000..febf32147d132 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_or_into_option.stderr @@ -0,0 +1,10 @@ +error: called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling `ok()` instead + --> $DIR/result_map_or_into_option.rs:7:13 + | +LL | let _ = opt.map_or(None, Some); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try using `ok` instead: `opt.ok()` + | + = note: `-D clippy::result-map-or-into-option` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.fixed b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.fixed new file mode 100644 index 0000000000000..1d0a3ecd0ff8d --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.fixed @@ -0,0 +1,80 @@ +// run-rustfix + +#![warn(clippy::result_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +struct HasResult { + field: Result, +} + +impl HasResult { + fn do_result_nothing(self: &Self, value: usize) {} + + fn do_result_plus_one(self: &Self, value: usize) -> usize { + value + 1 + } +} + +#[rustfmt::skip] +fn result_map_unit_fn() { + let x = HasResult { field: Ok(10) }; + + x.field.map(plus_one); + let _: Result<(), usize> = x.field.map(do_nothing); + + if let Ok(x_field) = x.field { do_nothing(x_field) } + + if let Ok(x_field) = x.field { do_nothing(x_field) } + + if let Ok(x_field) = x.field { diverge(x_field) } + + let captured = 10; + if let Ok(value) = x.field { do_nothing(value + captured) }; + let _: Result<(), usize> = x.field.map(|value| do_nothing(value + captured)); + + if let Ok(value) = x.field { x.do_result_nothing(value + captured) } + + if let Ok(value) = x.field { x.do_result_plus_one(value + captured); } + + + if let Ok(value) = x.field { do_nothing(value + captured) } + + if let Ok(value) = x.field { do_nothing(value + captured) } + + if let Ok(value) = x.field { do_nothing(value + captured); } + + if let Ok(value) = x.field { do_nothing(value + captured); } + + + if let Ok(value) = x.field { diverge(value + captured) } + + if let Ok(value) = x.field { diverge(value + captured) } + + if let Ok(value) = x.field { diverge(value + captured); } + + if let Ok(value) = x.field { diverge(value + captured); } + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + if let Ok(value) = x.field { let y = plus_one(value + captured); } + + if let Ok(value) = x.field { plus_one(value + captured); } + + if let Ok(value) = x.field { plus_one(value + captured); } + + + if let Ok(ref value) = x.field { do_nothing(value + captured) } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.rs b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.rs new file mode 100644 index 0000000000000..2fe18f923f08f --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.rs @@ -0,0 +1,80 @@ +// run-rustfix + +#![warn(clippy::result_map_unit_fn)] +#![allow(unused)] + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +struct HasResult { + field: Result, +} + +impl HasResult { + fn do_result_nothing(self: &Self, value: usize) {} + + fn do_result_plus_one(self: &Self, value: usize) -> usize { + value + 1 + } +} + +#[rustfmt::skip] +fn result_map_unit_fn() { + let x = HasResult { field: Ok(10) }; + + x.field.map(plus_one); + let _: Result<(), usize> = x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(do_nothing); + + x.field.map(diverge); + + let captured = 10; + if let Ok(value) = x.field { do_nothing(value + captured) }; + let _: Result<(), usize> = x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| x.do_result_nothing(value + captured)); + + x.field.map(|value| { x.do_result_plus_one(value + captured); }); + + + x.field.map(|value| do_nothing(value + captured)); + + x.field.map(|value| { do_nothing(value + captured) }); + + x.field.map(|value| { do_nothing(value + captured); }); + + x.field.map(|value| { { do_nothing(value + captured); } }); + + + x.field.map(|value| diverge(value + captured)); + + x.field.map(|value| { diverge(value + captured) }); + + x.field.map(|value| { diverge(value + captured); }); + + x.field.map(|value| { { diverge(value + captured); } }); + + + x.field.map(|value| plus_one(value + captured)); + x.field.map(|value| { plus_one(value + captured) }); + x.field.map(|value| { let y = plus_one(value + captured); }); + + x.field.map(|value| { plus_one(value + captured); }); + + x.field.map(|value| { { plus_one(value + captured); } }); + + + x.field.map(|ref value| { do_nothing(value + captured) }); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.stderr b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.stderr new file mode 100644 index 0000000000000..467e00263cd3a --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_fixable.stderr @@ -0,0 +1,140 @@ +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:35:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + | + = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:37:5 + | +LL | x.field.map(do_nothing); + | ^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(x_field) = x.field { do_nothing(x_field) }` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:39:5 + | +LL | x.field.map(diverge); + | ^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(x_field) = x.field { diverge(x_field) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:45:5 + | +LL | x.field.map(|value| x.do_result_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { x.do_result_nothing(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:47:5 + | +LL | x.field.map(|value| { x.do_result_plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { x.do_result_plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:50:5 + | +LL | x.field.map(|value| do_nothing(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:52:5 + | +LL | x.field.map(|value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:54:5 + | +LL | x.field.map(|value| { do_nothing(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:56:5 + | +LL | x.field.map(|value| { { do_nothing(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { do_nothing(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:59:5 + | +LL | x.field.map(|value| diverge(value + captured)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:61:5 + | +LL | x.field.map(|value| { diverge(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured) }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:63:5 + | +LL | x.field.map(|value| { diverge(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:65:5 + | +LL | x.field.map(|value| { { diverge(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { diverge(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:70:5 + | +LL | x.field.map(|value| { let y = plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { let y = plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:72:5 + | +LL | x.field.map(|value| { plus_one(value + captured); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:74:5 + | +LL | x.field.map(|value| { { plus_one(value + captured); } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { plus_one(value + captured); }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_fixable.rs:77:5 + | +LL | x.field.map(|ref value| { do_nothing(value + captured) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(ref value) = x.field { do_nothing(value + captured) }` + +error: aborting due to 17 previous errors + diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.rs b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.rs new file mode 100644 index 0000000000000..b197c609d7bfc --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.rs @@ -0,0 +1,46 @@ +#![warn(clippy::result_map_unit_fn)] +#![feature(never_type)] +#![allow(unused)] + +struct HasResult { + field: Result, +} + +fn do_nothing(_: T) {} + +fn diverge(_: T) -> ! { + panic!() +} + +fn plus_one(value: usize) -> usize { + value + 1 +} + +#[rustfmt::skip] +fn result_map_unit_fn() { + let x = HasResult { field: Ok(10) }; + + x.field.map(|value| { do_nothing(value); do_nothing(value) }); + + x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + + // Suggestion for the let block should be `{ ... }` as it's too difficult to build a + // proper suggestion for these cases + x.field.map(|value| { + do_nothing(value); + do_nothing(value) + }); + x.field.map(|value| { do_nothing(value); do_nothing(value); }); + + // The following should suggest `if let Ok(_X) ...` as it's difficult to generate a proper let variable name for them + let res: Result = Ok(42).map(diverge); + "12".parse::().map(diverge); + + let res: Result<(), usize> = Ok(plus_one(1)).map(do_nothing); + + // Should suggest `if let Ok(_y) ...` to not override the existing foo variable + let y: Result = Ok(42); + y.map(do_nothing); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.stderr b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.stderr new file mode 100644 index 0000000000000..b23cc608621d0 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unit_fn_unfixable.stderr @@ -0,0 +1,58 @@ +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_unfixable.rs:23:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { ... }` + | + = note: `-D clippy::result-map-unit-fn` implied by `-D warnings` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_unfixable.rs:25:5 + | +LL | x.field.map(|value| if value > 0 { do_nothing(value); do_nothing(value) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { ... }` + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_unfixable.rs:29:5 + | +LL | x.field.map(|value| { + | _____^ + | |_____| + | || +LL | || do_nothing(value); +LL | || do_nothing(value) +LL | || }); + | ||______^- help: try this: `if let Ok(value) = x.field { ... }` + | |_______| + | + +error: called `map(f)` on an `Result` value where `f` is a closure that returns the unit type + --> $DIR/result_map_unit_fn_unfixable.rs:33:5 + | +LL | x.field.map(|value| { do_nothing(value); do_nothing(value); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(value) = x.field { ... }` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type + --> $DIR/result_map_unit_fn_unfixable.rs:37:5 + | +LL | "12".parse::().map(diverge); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(a) = "12".parse::() { diverge(a) }` + +error: called `map(f)` on an `Result` value where `f` is a function that returns the unit type + --> $DIR/result_map_unit_fn_unfixable.rs:43:5 + | +LL | y.map(do_nothing); + | ^^^^^^^^^^^^^^^^^- + | | + | help: try this: `if let Ok(_y) = y { do_nothing(_y) }` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/result_map_unwrap_or_else.rs b/src/tools/clippy/tests/ui/result_map_unwrap_or_else.rs new file mode 100644 index 0000000000000..40751bfebe6c9 --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unwrap_or_else.rs @@ -0,0 +1,23 @@ +// aux-build:option_helpers.rs + +//! Checks implementation of `RESULT_MAP_UNWRAP_OR_ELSE` + +#![warn(clippy::result_map_unwrap_or_else)] + +#[macro_use] +extern crate option_helpers; + +fn result_methods() { + let res: Result = Ok(1); + + // Check RESULT_MAP_UNWRAP_OR_ELSE + // single line case + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line + // multi line cases + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + // macro case + let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|e| 0); // should not lint +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/result_map_unwrap_or_else.stderr b/src/tools/clippy/tests/ui/result_map_unwrap_or_else.stderr new file mode 100644 index 0000000000000..ec7bc8f12414f --- /dev/null +++ b/src/tools/clippy/tests/ui/result_map_unwrap_or_else.stderr @@ -0,0 +1,27 @@ +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/result_map_unwrap_or_else.rs:15:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); // should lint even though this call is on a separate line + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::result-map-unwrap-or-else` implied by `-D warnings` + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/result_map_unwrap_or_else.rs:17:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: called `map(f).unwrap_or_else(g)` on a `Result` value. This can be done more directly by calling `.map_or_else(g, f)` instead + --> $DIR/result_map_unwrap_or_else.rs:18:13 + | +LL | let _ = res.map(|x| x + 1).unwrap_or_else(|e| 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: replace `map(|x| x + 1).unwrap_or_else(|e| 0)` with `map_or_else(|e| 0, |x| x + 1)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/same_functions_in_if_condition.rs b/src/tools/clippy/tests/ui/same_functions_in_if_condition.rs new file mode 100644 index 0000000000000..686867cf5c6f6 --- /dev/null +++ b/src/tools/clippy/tests/ui/same_functions_in_if_condition.rs @@ -0,0 +1,80 @@ +#![warn(clippy::same_functions_in_if_condition)] +#![allow(clippy::ifs_same_cond)] // This warning is different from `ifs_same_cond`. +#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks + +fn function() -> bool { + true +} + +fn fn_arg(_arg: u8) -> bool { + true +} + +struct Struct; + +impl Struct { + fn method(&self) -> bool { + true + } + fn method_arg(&self, _arg: u8) -> bool { + true + } +} + +fn ifs_same_cond_fn() { + let a = 0; + let obj = Struct; + + if function() { + } else if function() { + //~ ERROR ifs same condition + } + + if fn_arg(a) { + } else if fn_arg(a) { + //~ ERROR ifs same condition + } + + if obj.method() { + } else if obj.method() { + //~ ERROR ifs same condition + } + + if obj.method_arg(a) { + } else if obj.method_arg(a) { + //~ ERROR ifs same condition + } + + let mut v = vec![1]; + if v.pop() == None { + //~ ERROR ifs same condition + } else if v.pop() == None { + } + + if v.len() == 42 { + //~ ERROR ifs same condition + } else if v.len() == 42 { + } + + if v.len() == 1 { + // ok, different conditions + } else if v.len() == 2 { + } + + if fn_arg(0) { + // ok, different arguments. + } else if fn_arg(1) { + } + + if obj.method_arg(0) { + // ok, different arguments. + } else if obj.method_arg(1) { + } + + if a == 1 { + // ok, warning is on `ifs_same_cond` behalf. + } else if a == 1 { + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr b/src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr new file mode 100644 index 0000000000000..363a03846d236 --- /dev/null +++ b/src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr @@ -0,0 +1,75 @@ +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:29:15 + | +LL | } else if function() { + | ^^^^^^^^^^ + | + = note: `-D clippy::same-functions-in-if-condition` implied by `-D warnings` +note: same as this + --> $DIR/same_functions_in_if_condition.rs:28:8 + | +LL | if function() { + | ^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:34:15 + | +LL | } else if fn_arg(a) { + | ^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:33:8 + | +LL | if fn_arg(a) { + | ^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:39:15 + | +LL | } else if obj.method() { + | ^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:38:8 + | +LL | if obj.method() { + | ^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:44:15 + | +LL | } else if obj.method_arg(a) { + | ^^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:43:8 + | +LL | if obj.method_arg(a) { + | ^^^^^^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:51:15 + | +LL | } else if v.pop() == None { + | ^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:49:8 + | +LL | if v.pop() == None { + | ^^^^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous `if` + --> $DIR/same_functions_in_if_condition.rs:56:15 + | +LL | } else if v.len() == 42 { + | ^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:54:8 + | +LL | if v.len() == 42 { + | ^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/serde.rs b/src/tools/clippy/tests/ui/serde.rs new file mode 100644 index 0000000000000..5843344eba89a --- /dev/null +++ b/src/tools/clippy/tests/ui/serde.rs @@ -0,0 +1,47 @@ +#![warn(clippy::serde_api_misuse)] +#![allow(dead_code)] + +extern crate serde; + +struct A; + +impl<'de> serde::de::Visitor<'de> for A { + type Value = (); + + fn expecting(&self, _: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + unimplemented!() + } + + fn visit_str(self, _v: &str) -> Result + where + E: serde::de::Error, + { + unimplemented!() + } + + fn visit_string(self, _v: String) -> Result + where + E: serde::de::Error, + { + unimplemented!() + } +} + +struct B; + +impl<'de> serde::de::Visitor<'de> for B { + type Value = (); + + fn expecting(&self, _: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + unimplemented!() + } + + fn visit_string(self, _v: String) -> Result + where + E: serde::de::Error, + { + unimplemented!() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/serde.stderr b/src/tools/clippy/tests/ui/serde.stderr new file mode 100644 index 0000000000000..760c9c9908a6f --- /dev/null +++ b/src/tools/clippy/tests/ui/serde.stderr @@ -0,0 +1,15 @@ +error: you should not implement `visit_string` without also implementing `visit_str` + --> $DIR/serde.rs:39:5 + | +LL | / fn visit_string(self, _v: String) -> Result +LL | | where +LL | | E: serde::de::Error, +LL | | { +LL | | unimplemented!() +LL | | } + | |_____^ + | + = note: `-D clippy::serde-api-misuse` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/shadow.rs b/src/tools/clippy/tests/ui/shadow.rs new file mode 100644 index 0000000000000..bd91ae4e9340c --- /dev/null +++ b/src/tools/clippy/tests/ui/shadow.rs @@ -0,0 +1,53 @@ +#![warn( + clippy::all, + clippy::pedantic, + clippy::shadow_same, + clippy::shadow_reuse, + clippy::shadow_unrelated +)] +#![allow( + unused_parens, + unused_variables, + clippy::missing_docs_in_private_items, + clippy::single_match +)] + +fn id(x: T) -> T { + x +} + +#[must_use] +fn first(x: (isize, isize)) -> isize { + x.0 +} + +fn main() { + let mut x = 1; + let x = &mut x; + let x = { x }; + let x = (&*x); + let x = { *x + 1 }; + let x = id(x); + let x = (1, x); + let x = first(x); + let y = 1; + let x = y; + + let x; + x = 42; + + let o = Some(1_u8); + + if let Some(p) = o { + assert_eq!(1, p); + } + match o { + Some(p) => p, // no error, because the p above is in its own scope + None => 0, + }; + + match (x, o) { + (1, Some(a)) | (a, Some(1)) => (), // no error though `a` appears twice + _ => (), + } +} diff --git a/src/tools/clippy/tests/ui/shadow.stderr b/src/tools/clippy/tests/ui/shadow.stderr new file mode 100644 index 0000000000000..7fa58cf76499b --- /dev/null +++ b/src/tools/clippy/tests/ui/shadow.stderr @@ -0,0 +1,138 @@ +error: `x` is shadowed by itself in `&mut x` + --> $DIR/shadow.rs:26:5 + | +LL | let x = &mut x; + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::shadow-same` implied by `-D warnings` +note: previous binding is here + --> $DIR/shadow.rs:25:13 + | +LL | let mut x = 1; + | ^ + +error: `x` is shadowed by itself in `{ x }` + --> $DIR/shadow.rs:27:5 + | +LL | let x = { x }; + | ^^^^^^^^^^^^^^ + | +note: previous binding is here + --> $DIR/shadow.rs:26:9 + | +LL | let x = &mut x; + | ^ + +error: `x` is shadowed by itself in `(&*x)` + --> $DIR/shadow.rs:28:5 + | +LL | let x = (&*x); + | ^^^^^^^^^^^^^^ + | +note: previous binding is here + --> $DIR/shadow.rs:27:9 + | +LL | let x = { x }; + | ^ + +error: `x` is shadowed by `{ *x + 1 }` which reuses the original value + --> $DIR/shadow.rs:29:9 + | +LL | let x = { *x + 1 }; + | ^ + | + = note: `-D clippy::shadow-reuse` implied by `-D warnings` +note: initialization happens here + --> $DIR/shadow.rs:29:13 + | +LL | let x = { *x + 1 }; + | ^^^^^^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:28:9 + | +LL | let x = (&*x); + | ^ + +error: `x` is shadowed by `id(x)` which reuses the original value + --> $DIR/shadow.rs:30:9 + | +LL | let x = id(x); + | ^ + | +note: initialization happens here + --> $DIR/shadow.rs:30:13 + | +LL | let x = id(x); + | ^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:29:9 + | +LL | let x = { *x + 1 }; + | ^ + +error: `x` is shadowed by `(1, x)` which reuses the original value + --> $DIR/shadow.rs:31:9 + | +LL | let x = (1, x); + | ^ + | +note: initialization happens here + --> $DIR/shadow.rs:31:13 + | +LL | let x = (1, x); + | ^^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:30:9 + | +LL | let x = id(x); + | ^ + +error: `x` is shadowed by `first(x)` which reuses the original value + --> $DIR/shadow.rs:32:9 + | +LL | let x = first(x); + | ^ + | +note: initialization happens here + --> $DIR/shadow.rs:32:13 + | +LL | let x = first(x); + | ^^^^^^^^ +note: previous binding is here + --> $DIR/shadow.rs:31:9 + | +LL | let x = (1, x); + | ^ + +error: `x` is shadowed by `y` + --> $DIR/shadow.rs:34:9 + | +LL | let x = y; + | ^ + | + = note: `-D clippy::shadow-unrelated` implied by `-D warnings` +note: initialization happens here + --> $DIR/shadow.rs:34:13 + | +LL | let x = y; + | ^ +note: previous binding is here + --> $DIR/shadow.rs:32:9 + | +LL | let x = first(x); + | ^ + +error: `x` shadows a previous declaration + --> $DIR/shadow.rs:36:5 + | +LL | let x; + | ^^^^^^ + | +note: previous binding is here + --> $DIR/shadow.rs:34:9 + | +LL | let x = y; + | ^ + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/short_circuit_statement.fixed b/src/tools/clippy/tests/ui/short_circuit_statement.fixed new file mode 100644 index 0000000000000..af0a397bd1aff --- /dev/null +++ b/src/tools/clippy/tests/ui/short_circuit_statement.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::short_circuit_statement)] +#![allow(clippy::nonminimal_bool)] + +fn main() { + if f() { g(); } + if !f() { g(); } + if !(1 == 2) { g(); } +} + +fn f() -> bool { + true +} + +fn g() -> bool { + false +} diff --git a/src/tools/clippy/tests/ui/short_circuit_statement.rs b/src/tools/clippy/tests/ui/short_circuit_statement.rs new file mode 100644 index 0000000000000..73a55bf1f5e27 --- /dev/null +++ b/src/tools/clippy/tests/ui/short_circuit_statement.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::short_circuit_statement)] +#![allow(clippy::nonminimal_bool)] + +fn main() { + f() && g(); + f() || g(); + 1 == 2 || g(); +} + +fn f() -> bool { + true +} + +fn g() -> bool { + false +} diff --git a/src/tools/clippy/tests/ui/short_circuit_statement.stderr b/src/tools/clippy/tests/ui/short_circuit_statement.stderr new file mode 100644 index 0000000000000..0a3f60c3d132d --- /dev/null +++ b/src/tools/clippy/tests/ui/short_circuit_statement.stderr @@ -0,0 +1,22 @@ +error: boolean short circuit operator in statement may be clearer using an explicit test + --> $DIR/short_circuit_statement.rs:7:5 + | +LL | f() && g(); + | ^^^^^^^^^^^ help: replace it with: `if f() { g(); }` + | + = note: `-D clippy::short-circuit-statement` implied by `-D warnings` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> $DIR/short_circuit_statement.rs:8:5 + | +LL | f() || g(); + | ^^^^^^^^^^^ help: replace it with: `if !f() { g(); }` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> $DIR/short_circuit_statement.rs:9:5 + | +LL | 1 == 2 || g(); + | ^^^^^^^^^^^^^^ help: replace it with: `if !(1 == 2) { g(); }` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/similar_names.rs b/src/tools/clippy/tests/ui/similar_names.rs new file mode 100644 index 0000000000000..6796b15289ecd --- /dev/null +++ b/src/tools/clippy/tests/ui/similar_names.rs @@ -0,0 +1,103 @@ +#![warn(clippy::similar_names)] +#![allow(unused, clippy::println_empty_string)] + +struct Foo { + apple: i32, + bpple: i32, +} + +fn main() { + let specter: i32; + let spectre: i32; + + let apple: i32; + + let bpple: i32; + + let cpple: i32; + + let a_bar: i32; + let b_bar: i32; + let c_bar: i32; + + let items = [5]; + for item in &items { + loop {} + } + + let foo_x: i32; + let foo_y: i32; + + let rhs: i32; + let lhs: i32; + + let bla_rhs: i32; + let bla_lhs: i32; + + let blubrhs: i32; + let blublhs: i32; + + let blubx: i32; + let bluby: i32; + + let cake: i32; + let cakes: i32; + let coke: i32; + + match 5 { + cheese @ 1 => {}, + rabbit => panic!(), + } + let cheese: i32; + match (42, 43) { + (cheese1, 1) => {}, + (cheese2, 2) => panic!(), + _ => println!(""), + } + let ipv4: i32; + let ipv6: i32; + let abcd1: i32; + let abdc2: i32; + let xyz1abc: i32; + let xyz2abc: i32; + let xyzeabc: i32; + + let parser: i32; + let parsed: i32; + let parsee: i32; + + let setter: i32; + let getter: i32; + let tx1: i32; + let rx1: i32; + let tx_cake: i32; + let rx_cake: i32; +} + +fn foo() { + let Foo { apple, bpple } = unimplemented!(); + let Foo { + apple: spring, + bpple: sprang, + } = unimplemented!(); +} + +// false positive similar_names (#3057, #2651) +// clippy claimed total_reg_src_size and total_size and +// numb_reg_src_checkouts and total_bin_size were similar +#[derive(Debug, Clone)] +pub(crate) struct DirSizes { + pub(crate) total_size: u64, + pub(crate) numb_bins: u64, + pub(crate) total_bin_size: u64, + pub(crate) total_reg_size: u64, + pub(crate) total_git_db_size: u64, + pub(crate) total_git_repos_bare_size: u64, + pub(crate) numb_git_repos_bare_repos: u64, + pub(crate) numb_git_checkouts: u64, + pub(crate) total_git_chk_size: u64, + pub(crate) total_reg_cache_size: u64, + pub(crate) total_reg_src_size: u64, + pub(crate) numb_reg_cache_entries: u64, + pub(crate) numb_reg_src_checkouts: u64, +} diff --git a/src/tools/clippy/tests/ui/similar_names.stderr b/src/tools/clippy/tests/ui/similar_names.stderr new file mode 100644 index 0000000000000..0256f126a94fc --- /dev/null +++ b/src/tools/clippy/tests/ui/similar_names.stderr @@ -0,0 +1,107 @@ +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:15:9 + | +LL | let bpple: i32; + | ^^^^^ + | + = note: `-D clippy::similar-names` implied by `-D warnings` +note: existing binding defined here + --> $DIR/similar_names.rs:13:9 + | +LL | let apple: i32; + | ^^^^^ +help: separate the discriminating character by an underscore like: `b_pple` + --> $DIR/similar_names.rs:15:9 + | +LL | let bpple: i32; + | ^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:17:9 + | +LL | let cpple: i32; + | ^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:13:9 + | +LL | let apple: i32; + | ^^^^^ +help: separate the discriminating character by an underscore like: `c_pple` + --> $DIR/similar_names.rs:17:9 + | +LL | let cpple: i32; + | ^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:41:9 + | +LL | let bluby: i32; + | ^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:40:9 + | +LL | let blubx: i32; + | ^^^^^ +help: separate the discriminating character by an underscore like: `blub_y` + --> $DIR/similar_names.rs:41:9 + | +LL | let bluby: i32; + | ^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:45:9 + | +LL | let coke: i32; + | ^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:43:9 + | +LL | let cake: i32; + | ^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:63:9 + | +LL | let xyzeabc: i32; + | ^^^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:61:9 + | +LL | let xyz1abc: i32; + | ^^^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:67:9 + | +LL | let parsee: i32; + | ^^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:65:9 + | +LL | let parser: i32; + | ^^^^^^ +help: separate the discriminating character by an underscore like: `parse_e` + --> $DIR/similar_names.rs:67:9 + | +LL | let parsee: i32; + | ^^^^^^ + +error: binding's name is too similar to existing binding + --> $DIR/similar_names.rs:81:16 + | +LL | bpple: sprang, + | ^^^^^^ + | +note: existing binding defined here + --> $DIR/similar_names.rs:80:16 + | +LL | apple: spring, + | ^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/single_char_pattern.fixed b/src/tools/clippy/tests/ui/single_char_pattern.fixed new file mode 100644 index 0000000000000..3871c4f2268cc --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_pattern.fixed @@ -0,0 +1,63 @@ +// run-rustfix + +#![allow(unused_must_use)] + +use std::collections::HashSet; + +fn main() { + let x = "foo"; + x.split('x'); + x.split("xx"); + x.split('x'); + + let y = "x"; + x.split(y); + // Not yet testing for multi-byte characters + // Changing `r.len() == 1` to `r.chars().count() == 1` in `lint_clippy::single_char_pattern` + // should have done this but produced an ICE + // + // We may not want to suggest changing these anyway + // See: https://github.com/rust-lang/rust-clippy/issues/650#issuecomment-184328984 + x.split("ß"); + x.split("ℝ"); + x.split("💣"); + // Can't use this lint for unicode code points which don't fit in a char + x.split("❤️"); + x.contains('x'); + x.starts_with('x'); + x.ends_with('x'); + x.find('x'); + x.rfind('x'); + x.rsplit('x'); + x.split_terminator('x'); + x.rsplit_terminator('x'); + x.splitn(0, 'x'); + x.rsplitn(0, 'x'); + x.matches('x'); + x.rmatches('x'); + x.match_indices('x'); + x.rmatch_indices('x'); + x.trim_start_matches('x'); + x.trim_end_matches('x'); + // Make sure we escape characters correctly. + x.split('\n'); + x.split('\''); + x.split('\''); + + let h = HashSet::::new(); + h.contains("X"); // should not warn + + x.replace(";", ",").split(','); // issue #2978 + x.starts_with('\x03'); // issue #2996 + + // Issue #3204 + const S: &str = "#"; + x.find(S); + + // Raw string + x.split('a'); + x.split('a'); + x.split('a'); + x.split('\''); + x.split('#'); +} diff --git a/src/tools/clippy/tests/ui/single_char_pattern.rs b/src/tools/clippy/tests/ui/single_char_pattern.rs new file mode 100644 index 0000000000000..32afe339cd81c --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_pattern.rs @@ -0,0 +1,63 @@ +// run-rustfix + +#![allow(unused_must_use)] + +use std::collections::HashSet; + +fn main() { + let x = "foo"; + x.split("x"); + x.split("xx"); + x.split('x'); + + let y = "x"; + x.split(y); + // Not yet testing for multi-byte characters + // Changing `r.len() == 1` to `r.chars().count() == 1` in `lint_clippy::single_char_pattern` + // should have done this but produced an ICE + // + // We may not want to suggest changing these anyway + // See: https://github.com/rust-lang/rust-clippy/issues/650#issuecomment-184328984 + x.split("ß"); + x.split("ℝ"); + x.split("💣"); + // Can't use this lint for unicode code points which don't fit in a char + x.split("❤️"); + x.contains("x"); + x.starts_with("x"); + x.ends_with("x"); + x.find("x"); + x.rfind("x"); + x.rsplit("x"); + x.split_terminator("x"); + x.rsplit_terminator("x"); + x.splitn(0, "x"); + x.rsplitn(0, "x"); + x.matches("x"); + x.rmatches("x"); + x.match_indices("x"); + x.rmatch_indices("x"); + x.trim_start_matches("x"); + x.trim_end_matches("x"); + // Make sure we escape characters correctly. + x.split("\n"); + x.split("'"); + x.split("\'"); + + let h = HashSet::::new(); + h.contains("X"); // should not warn + + x.replace(";", ",").split(","); // issue #2978 + x.starts_with("\x03"); // issue #2996 + + // Issue #3204 + const S: &str = "#"; + x.find(S); + + // Raw string + x.split(r"a"); + x.split(r#"a"#); + x.split(r###"a"###); + x.split(r###"'"###); + x.split(r###"#"###); +} diff --git a/src/tools/clippy/tests/ui/single_char_pattern.stderr b/src/tools/clippy/tests/ui/single_char_pattern.stderr new file mode 100644 index 0000000000000..fe7211c53f852 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_char_pattern.stderr @@ -0,0 +1,166 @@ +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:9:13 + | +LL | x.split("x"); + | ^^^ help: try using a `char` instead: `'x'` + | + = note: `-D clippy::single-char-pattern` implied by `-D warnings` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:26:16 + | +LL | x.contains("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:27:19 + | +LL | x.starts_with("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:28:17 + | +LL | x.ends_with("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:29:12 + | +LL | x.find("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:30:13 + | +LL | x.rfind("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:31:14 + | +LL | x.rsplit("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:32:24 + | +LL | x.split_terminator("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:33:25 + | +LL | x.rsplit_terminator("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:34:17 + | +LL | x.splitn(0, "x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:35:18 + | +LL | x.rsplitn(0, "x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:36:15 + | +LL | x.matches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:37:16 + | +LL | x.rmatches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:38:21 + | +LL | x.match_indices("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:39:22 + | +LL | x.rmatch_indices("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:40:26 + | +LL | x.trim_start_matches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:41:24 + | +LL | x.trim_end_matches("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:43:13 + | +LL | x.split("/n"); + | ^^^^ help: try using a `char` instead: `'/n'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:44:13 + | +LL | x.split("'"); + | ^^^ help: try using a `char` instead: `'/''` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:45:13 + | +LL | x.split("/'"); + | ^^^^ help: try using a `char` instead: `'/''` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:50:31 + | +LL | x.replace(";", ",").split(","); // issue #2978 + | ^^^ help: try using a `char` instead: `','` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:51:19 + | +LL | x.starts_with("/x03"); // issue #2996 + | ^^^^^^ help: try using a `char` instead: `'/x03'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:58:13 + | +LL | x.split(r"a"); + | ^^^^ help: try using a `char` instead: `'a'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:59:13 + | +LL | x.split(r#"a"#); + | ^^^^^^ help: try using a `char` instead: `'a'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:60:13 + | +LL | x.split(r###"a"###); + | ^^^^^^^^^^ help: try using a `char` instead: `'a'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:61:13 + | +LL | x.split(r###"'"###); + | ^^^^^^^^^^ help: try using a `char` instead: `'/''` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:62:13 + | +LL | x.split(r###"#"###); + | ^^^^^^^^^^ help: try using a `char` instead: `'#'` + +error: aborting due to 27 previous errors + diff --git a/src/tools/clippy/tests/ui/single_component_path_imports.fixed b/src/tools/clippy/tests/ui/single_component_path_imports.fixed new file mode 100644 index 0000000000000..a7a8499b58f00 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_component_path_imports.fixed @@ -0,0 +1,21 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::single_component_path_imports)] +#![allow(unused_imports)] + + +use serde as edres; +pub use serde; + +macro_rules! m { + () => { + use regex; + }; +} + +fn main() { + regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + + // False positive #5154, shouldn't trigger lint. + m!(); +} diff --git a/src/tools/clippy/tests/ui/single_component_path_imports.rs b/src/tools/clippy/tests/ui/single_component_path_imports.rs new file mode 100644 index 0000000000000..9a427e90ad3df --- /dev/null +++ b/src/tools/clippy/tests/ui/single_component_path_imports.rs @@ -0,0 +1,21 @@ +// run-rustfix +// edition:2018 +#![warn(clippy::single_component_path_imports)] +#![allow(unused_imports)] + +use regex; +use serde as edres; +pub use serde; + +macro_rules! m { + () => { + use regex; + }; +} + +fn main() { + regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); + + // False positive #5154, shouldn't trigger lint. + m!(); +} diff --git a/src/tools/clippy/tests/ui/single_component_path_imports.stderr b/src/tools/clippy/tests/ui/single_component_path_imports.stderr new file mode 100644 index 0000000000000..519ada0169a6d --- /dev/null +++ b/src/tools/clippy/tests/ui/single_component_path_imports.stderr @@ -0,0 +1,10 @@ +error: this import is redundant + --> $DIR/single_component_path_imports.rs:6:1 + | +LL | use regex; + | ^^^^^^^^^^ help: remove it entirely + | + = note: `-D clippy::single-component-path-imports` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/single_match.rs b/src/tools/clippy/tests/ui/single_match.rs new file mode 100644 index 0000000000000..1c55af5dfb673 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match.rs @@ -0,0 +1,95 @@ +#![warn(clippy::single_match)] + +fn dummy() {} + +fn single_match() { + let x = Some(1u8); + + match x { + Some(y) => { + println!("{:?}", y); + }, + _ => (), + }; + + let x = Some(1u8); + match x { + // Note the missing block braces. + // We suggest `if let Some(y) = x { .. }` because the macro + // is expanded before we can do anything. + Some(y) => println!("{:?}", y), + _ => (), + } + + let z = (1u8, 1u8); + match z { + (2..=3, 7..=9) => dummy(), + _ => {}, + }; + + // Not linted (pattern guards used) + match x { + Some(y) if y == 0 => println!("{:?}", y), + _ => (), + } + + // Not linted (no block with statements in the single arm) + match z { + (2..=3, 7..=9) => println!("{:?}", z), + _ => println!("nope"), + } +} + +enum Foo { + Bar, + Baz(u8), +} +use std::borrow::Cow; +use Foo::*; + +fn single_match_know_enum() { + let x = Some(1u8); + let y: Result<_, i8> = Ok(1i8); + + match x { + Some(y) => dummy(), + None => (), + }; + + match y { + Ok(y) => dummy(), + Err(..) => (), + }; + + let c = Cow::Borrowed(""); + + match c { + Cow::Borrowed(..) => dummy(), + Cow::Owned(..) => (), + }; + + let z = Foo::Bar; + // no warning + match z { + Bar => println!("42"), + Baz(_) => (), + } + + match z { + Baz(_) => println!("42"), + Bar => (), + } +} + +macro_rules! single_match { + ($num:literal) => { + match $num { + 15 => println!("15"), + _ => (), + } + }; +} + +fn main() { + single_match!(5); +} diff --git a/src/tools/clippy/tests/ui/single_match.stderr b/src/tools/clippy/tests/ui/single_match.stderr new file mode 100644 index 0000000000000..f69554d75f9bf --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match.stderr @@ -0,0 +1,69 @@ +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:8:5 + | +LL | / match x { +LL | | Some(y) => { +LL | | println!("{:?}", y); +LL | | }, +LL | | _ => (), +LL | | }; + | |_____^ + | + = note: `-D clippy::single-match` implied by `-D warnings` +help: try this + | +LL | if let Some(y) = x { +LL | println!("{:?}", y); +LL | }; + | + +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:16:5 + | +LL | / match x { +LL | | // Note the missing block braces. +LL | | // We suggest `if let Some(y) = x { .. }` because the macro +LL | | // is expanded before we can do anything. +LL | | Some(y) => println!("{:?}", y), +LL | | _ => (), +LL | | } + | |_____^ help: try this: `if let Some(y) = x { println!("{:?}", y) }` + +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:25:5 + | +LL | / match z { +LL | | (2..=3, 7..=9) => dummy(), +LL | | _ => {}, +LL | | }; + | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }` + +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:54:5 + | +LL | / match x { +LL | | Some(y) => dummy(), +LL | | None => (), +LL | | }; + | |_____^ help: try this: `if let Some(y) = x { dummy() }` + +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:59:5 + | +LL | / match y { +LL | | Ok(y) => dummy(), +LL | | Err(..) => (), +LL | | }; + | |_____^ help: try this: `if let Ok(y) = y { dummy() }` + +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:66:5 + | +LL | / match c { +LL | | Cow::Borrowed(..) => dummy(), +LL | | Cow::Owned(..) => (), +LL | | }; + | |_____^ help: try this: `if let Cow::Borrowed(..) = c { dummy() }` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/single_match_else.rs b/src/tools/clippy/tests/ui/single_match_else.rs new file mode 100644 index 0000000000000..34193be0b75e4 --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else.rs @@ -0,0 +1,35 @@ +#![warn(clippy::single_match_else)] + +enum ExprNode { + ExprAddrOf, + Butterflies, + Unicorns, +} + +static NODE: ExprNode = ExprNode::Unicorns; + +fn unwrap_addr() -> Option<&'static ExprNode> { + match ExprNode::Butterflies { + ExprNode::ExprAddrOf => Some(&NODE), + _ => { + let x = 5; + None + }, + } +} + +macro_rules! unwrap_addr { + ($expression:expr) => { + match $expression { + ExprNode::ExprAddrOf => Some(&NODE), + _ => { + let x = 5; + None + }, + } + }; +} + +fn main() { + unwrap_addr!(ExprNode::Unicorns); +} diff --git a/src/tools/clippy/tests/ui/single_match_else.stderr b/src/tools/clippy/tests/ui/single_match_else.stderr new file mode 100644 index 0000000000000..59861d46eb34c --- /dev/null +++ b/src/tools/clippy/tests/ui/single_match_else.stderr @@ -0,0 +1,23 @@ +error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:12:5 + | +LL | / match ExprNode::Butterflies { +LL | | ExprNode::ExprAddrOf => Some(&NODE), +LL | | _ => { +LL | | let x = 5; +LL | | None +LL | | }, +LL | | } + | |_____^ + | + = note: `-D clippy::single-match-else` implied by `-D warnings` +help: try this + | +LL | if let ExprNode::ExprAddrOf = ExprNode::Butterflies { Some(&NODE) } else { +LL | let x = 5; +LL | None +LL | } + | + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/skip_while_next.rs b/src/tools/clippy/tests/ui/skip_while_next.rs new file mode 100644 index 0000000000000..a522c0f08b207 --- /dev/null +++ b/src/tools/clippy/tests/ui/skip_while_next.rs @@ -0,0 +1,29 @@ +// aux-build:option_helpers.rs + +#![warn(clippy::skip_while_next)] +#![allow(clippy::blacklisted_name)] + +extern crate option_helpers; +use option_helpers::IteratorFalsePositives; + +#[rustfmt::skip] +fn skip_while_next() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Single-line case. + let _ = v.iter().skip_while(|&x| *x < 0).next(); + + // Multi-line case. + let _ = v.iter().skip_while(|&x| { + *x < 0 + } + ).next(); + + // Check that hat we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.skip_while().next(); +} + +fn main() { + skip_while_next(); +} diff --git a/src/tools/clippy/tests/ui/skip_while_next.stderr b/src/tools/clippy/tests/ui/skip_while_next.stderr new file mode 100644 index 0000000000000..a6b7bcd63ff39 --- /dev/null +++ b/src/tools/clippy/tests/ui/skip_while_next.stderr @@ -0,0 +1,23 @@ +error: called `skip_while(p).next()` on an `Iterator` + --> $DIR/skip_while_next.rs:14:13 + | +LL | let _ = v.iter().skip_while(|&x| *x < 0).next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::skip-while-next` implied by `-D warnings` + = help: this is more succinctly expressed by calling `.find(!p)` instead + +error: called `skip_while(p).next()` on an `Iterator` + --> $DIR/skip_while_next.rs:17:13 + | +LL | let _ = v.iter().skip_while(|&x| { + | _____________^ +LL | | *x < 0 +LL | | } +LL | | ).next(); + | |___________________________^ + | + = help: this is more succinctly expressed by calling `.find(!p)` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.rs b/src/tools/clippy/tests/ui/slow_vector_initialization.rs new file mode 100644 index 0000000000000..c5ae3ff769b11 --- /dev/null +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.rs @@ -0,0 +1,63 @@ +use std::iter::repeat; + +fn main() { + resize_vector(); + extend_vector(); + mixed_extend_resize_vector(); +} + +fn extend_vector() { + // Extend with constant expression + let len = 300; + let mut vec1 = Vec::with_capacity(len); + vec1.extend(repeat(0).take(len)); + + // Extend with len expression + let mut vec2 = Vec::with_capacity(len - 10); + vec2.extend(repeat(0).take(len - 10)); + + // Extend with mismatching expression should not be warned + let mut vec3 = Vec::with_capacity(24322); + vec3.extend(repeat(0).take(2)); +} + +fn mixed_extend_resize_vector() { + // Mismatching len + let mut mismatching_len = Vec::with_capacity(30); + mismatching_len.extend(repeat(0).take(40)); + + // Slow initialization + let mut resized_vec = Vec::with_capacity(30); + resized_vec.resize(30, 0); + + let mut extend_vec = Vec::with_capacity(30); + extend_vec.extend(repeat(0).take(30)); +} + +fn resize_vector() { + // Resize with constant expression + let len = 300; + let mut vec1 = Vec::with_capacity(len); + vec1.resize(len, 0); + + // Resize mismatch len + let mut vec2 = Vec::with_capacity(200); + vec2.resize(10, 0); + + // Resize with len expression + let mut vec3 = Vec::with_capacity(len - 10); + vec3.resize(len - 10, 0); + + // Reinitialization should be warned + vec1 = Vec::with_capacity(10); + vec1.resize(10, 0); +} + +fn do_stuff(vec: &mut Vec) {} + +fn extend_vector_with_manipulations_between() { + let len = 300; + let mut vec1: Vec = Vec::with_capacity(len); + do_stuff(&mut vec1); + vec1.extend(repeat(0).take(len)); +} diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.stderr b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr new file mode 100644 index 0000000000000..5d2788ec26086 --- /dev/null +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr @@ -0,0 +1,60 @@ +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:13:5 + | +LL | let mut vec1 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec1.extend(repeat(0).take(len)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::slow-vector-initialization` implied by `-D warnings` + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:17:5 + | +LL | let mut vec2 = Vec::with_capacity(len - 10); + | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` +LL | vec2.extend(repeat(0).take(len - 10)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:31:5 + | +LL | let mut resized_vec = Vec::with_capacity(30); + | ---------------------- help: consider replace allocation with: `vec![0; 30]` +LL | resized_vec.resize(30, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:34:5 + | +LL | let mut extend_vec = Vec::with_capacity(30); + | ---------------------- help: consider replace allocation with: `vec![0; 30]` +LL | extend_vec.extend(repeat(0).take(30)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:41:5 + | +LL | let mut vec1 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec1.resize(len, 0); + | ^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:49:5 + | +LL | let mut vec3 = Vec::with_capacity(len - 10); + | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` +LL | vec3.resize(len - 10, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:53:5 + | +LL | vec1 = Vec::with_capacity(10); + | ---------------------- help: consider replace allocation with: `vec![0; 10]` +LL | vec1.resize(10, 0); + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/starts_ends_with.fixed b/src/tools/clippy/tests/ui/starts_ends_with.fixed new file mode 100644 index 0000000000000..7dfcf9c91e486 --- /dev/null +++ b/src/tools/clippy/tests/ui/starts_ends_with.fixed @@ -0,0 +1,46 @@ +// run-rustfix +#![allow(dead_code, unused_must_use)] + +fn main() {} + +#[allow(clippy::unnecessary_operation)] +fn starts_with() { + "".starts_with(' '); + !"".starts_with(' '); +} + +fn chars_cmp_with_unwrap() { + let s = String::from("foo"); + if s.starts_with('f') { + // s.starts_with('f') + // Nothing here + } + if s.ends_with('o') { + // s.ends_with('o') + // Nothing here + } + if s.ends_with('o') { + // s.ends_with('o') + // Nothing here + } + if !s.starts_with('f') { + // !s.starts_with('f') + // Nothing here + } + if !s.ends_with('o') { + // !s.ends_with('o') + // Nothing here + } + if !s.ends_with('o') { + // !s.ends_with('o') + // Nothing here + } +} + +#[allow(clippy::unnecessary_operation)] +fn ends_with() { + "".ends_with(' '); + !"".ends_with(' '); + "".ends_with(' '); + !"".ends_with(' '); +} diff --git a/src/tools/clippy/tests/ui/starts_ends_with.rs b/src/tools/clippy/tests/ui/starts_ends_with.rs new file mode 100644 index 0000000000000..e48a424635439 --- /dev/null +++ b/src/tools/clippy/tests/ui/starts_ends_with.rs @@ -0,0 +1,46 @@ +// run-rustfix +#![allow(dead_code, unused_must_use)] + +fn main() {} + +#[allow(clippy::unnecessary_operation)] +fn starts_with() { + "".chars().next() == Some(' '); + Some(' ') != "".chars().next(); +} + +fn chars_cmp_with_unwrap() { + let s = String::from("foo"); + if s.chars().next().unwrap() == 'f' { + // s.starts_with('f') + // Nothing here + } + if s.chars().next_back().unwrap() == 'o' { + // s.ends_with('o') + // Nothing here + } + if s.chars().last().unwrap() == 'o' { + // s.ends_with('o') + // Nothing here + } + if s.chars().next().unwrap() != 'f' { + // !s.starts_with('f') + // Nothing here + } + if s.chars().next_back().unwrap() != 'o' { + // !s.ends_with('o') + // Nothing here + } + if s.chars().last().unwrap() != 'o' { + // !s.ends_with('o') + // Nothing here + } +} + +#[allow(clippy::unnecessary_operation)] +fn ends_with() { + "".chars().last() == Some(' '); + Some(' ') != "".chars().last(); + "".chars().next_back() == Some(' '); + Some(' ') != "".chars().next_back(); +} diff --git a/src/tools/clippy/tests/ui/starts_ends_with.stderr b/src/tools/clippy/tests/ui/starts_ends_with.stderr new file mode 100644 index 0000000000000..7c726d0e01026 --- /dev/null +++ b/src/tools/clippy/tests/ui/starts_ends_with.stderr @@ -0,0 +1,78 @@ +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:8:5 + | +LL | "".chars().next() == Some(' '); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".starts_with(' ')` + | + = note: `-D clippy::chars-next-cmp` implied by `-D warnings` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:9:5 + | +LL | Some(' ') != "".chars().next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".starts_with(' ')` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:14:8 + | +LL | if s.chars().next().unwrap() == 'f' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.starts_with('f')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:18:8 + | +LL | if s.chars().next_back().unwrap() == 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.ends_with('o')` + | + = note: `-D clippy::chars-last-cmp` implied by `-D warnings` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:22:8 + | +LL | if s.chars().last().unwrap() == 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.ends_with('o')` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:26:8 + | +LL | if s.chars().next().unwrap() != 'f' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.starts_with('f')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:30:8 + | +LL | if s.chars().next_back().unwrap() != 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('o')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:34:8 + | +LL | if s.chars().last().unwrap() != 'o' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('o')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:42:5 + | +LL | "".chars().last() == Some(' '); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with(' ')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:43:5 + | +LL | Some(' ') != "".chars().last(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with(' ')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:44:5 + | +LL | "".chars().next_back() == Some(' '); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with(' ')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:45:5 + | +LL | Some(' ') != "".chars().next_back(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with(' ')` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/string_add.rs b/src/tools/clippy/tests/ui/string_add.rs new file mode 100644 index 0000000000000..30fd17c59e518 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add.rs @@ -0,0 +1,26 @@ +// aux-build:macro_rules.rs + +#[macro_use] +extern crate macro_rules; + +#[warn(clippy::string_add)] +#[allow(clippy::string_add_assign, unused)] +fn main() { + // ignores assignment distinction + let mut x = "".to_owned(); + + for _ in 1..3 { + x = x + "."; + } + + let y = "".to_owned(); + let z = y + "..."; + + assert_eq!(&x, &z); + + let mut x = 1; + x = x + 1; + assert_eq!(2, x); + + string_add!(); +} diff --git a/src/tools/clippy/tests/ui/string_add.stderr b/src/tools/clippy/tests/ui/string_add.stderr new file mode 100644 index 0000000000000..3987641c75a30 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add.stderr @@ -0,0 +1,30 @@ +error: manual implementation of an assign operation + --> $DIR/string_add.rs:13:9 + | +LL | x = x + "."; + | ^^^^^^^^^^^ help: replace it with: `x += "."` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: you added something to a string. Consider using `String::push_str()` instead + --> $DIR/string_add.rs:13:13 + | +LL | x = x + "."; + | ^^^^^^^ + | + = note: `-D clippy::string-add` implied by `-D warnings` + +error: you added something to a string. Consider using `String::push_str()` instead + --> $DIR/string_add.rs:17:13 + | +LL | let z = y + "..."; + | ^^^^^^^^^ + +error: manual implementation of an assign operation + --> $DIR/string_add.rs:22:5 + | +LL | x = x + 1; + | ^^^^^^^^^ help: replace it with: `x += 1` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/string_add_assign.fixed b/src/tools/clippy/tests/ui/string_add_assign.fixed new file mode 100644 index 0000000000000..db71bab1e5214 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add_assign.fixed @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(clippy::string_add, unused)] +#[warn(clippy::string_add_assign)] +fn main() { + // ignores assignment distinction + let mut x = "".to_owned(); + + for _ in 1..3 { + x += "."; + } + + let y = "".to_owned(); + let z = y + "..."; + + assert_eq!(&x, &z); + + let mut x = 1; + x += 1; + assert_eq!(2, x); +} diff --git a/src/tools/clippy/tests/ui/string_add_assign.rs b/src/tools/clippy/tests/ui/string_add_assign.rs new file mode 100644 index 0000000000000..644991945cbe2 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add_assign.rs @@ -0,0 +1,21 @@ +// run-rustfix + +#[allow(clippy::string_add, unused)] +#[warn(clippy::string_add_assign)] +fn main() { + // ignores assignment distinction + let mut x = "".to_owned(); + + for _ in 1..3 { + x = x + "."; + } + + let y = "".to_owned(); + let z = y + "..."; + + assert_eq!(&x, &z); + + let mut x = 1; + x = x + 1; + assert_eq!(2, x); +} diff --git a/src/tools/clippy/tests/ui/string_add_assign.stderr b/src/tools/clippy/tests/ui/string_add_assign.stderr new file mode 100644 index 0000000000000..7676175c1b82f --- /dev/null +++ b/src/tools/clippy/tests/ui/string_add_assign.stderr @@ -0,0 +1,24 @@ +error: you assigned the result of adding something to this string. Consider using `String::push_str()` instead + --> $DIR/string_add_assign.rs:10:9 + | +LL | x = x + "."; + | ^^^^^^^^^^^ + | + = note: `-D clippy::string-add-assign` implied by `-D warnings` + +error: manual implementation of an assign operation + --> $DIR/string_add_assign.rs:10:9 + | +LL | x = x + "."; + | ^^^^^^^^^^^ help: replace it with: `x += "."` + | + = note: `-D clippy::assign-op-pattern` implied by `-D warnings` + +error: manual implementation of an assign operation + --> $DIR/string_add_assign.rs:19:5 + | +LL | x = x + 1; + | ^^^^^^^^^ help: replace it with: `x += 1` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/string_extend.fixed b/src/tools/clippy/tests/ui/string_extend.fixed new file mode 100644 index 0000000000000..1883a9f832578 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_extend.fixed @@ -0,0 +1,32 @@ +// run-rustfix + +#[derive(Copy, Clone)] +struct HasChars; + +impl HasChars { + fn chars(self) -> std::str::Chars<'static> { + "HasChars".chars() + } +} + +fn main() { + let abc = "abc"; + let def = String::from("def"); + let mut s = String::new(); + + s.push_str(abc); + s.push_str(abc); + + s.push_str("abc"); + s.push_str("abc"); + + s.push_str(&def); + s.push_str(&def); + + s.extend(abc.chars().skip(1)); + s.extend("abc".chars().skip(1)); + s.extend(['a', 'b', 'c'].iter()); + + let f = HasChars; + s.extend(f.chars()); +} diff --git a/src/tools/clippy/tests/ui/string_extend.rs b/src/tools/clippy/tests/ui/string_extend.rs new file mode 100644 index 0000000000000..07d0baa1be6c7 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_extend.rs @@ -0,0 +1,32 @@ +// run-rustfix + +#[derive(Copy, Clone)] +struct HasChars; + +impl HasChars { + fn chars(self) -> std::str::Chars<'static> { + "HasChars".chars() + } +} + +fn main() { + let abc = "abc"; + let def = String::from("def"); + let mut s = String::new(); + + s.push_str(abc); + s.extend(abc.chars()); + + s.push_str("abc"); + s.extend("abc".chars()); + + s.push_str(&def); + s.extend(def.chars()); + + s.extend(abc.chars().skip(1)); + s.extend("abc".chars().skip(1)); + s.extend(['a', 'b', 'c'].iter()); + + let f = HasChars; + s.extend(f.chars()); +} diff --git a/src/tools/clippy/tests/ui/string_extend.stderr b/src/tools/clippy/tests/ui/string_extend.stderr new file mode 100644 index 0000000000000..6af8c9e1662b5 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_extend.stderr @@ -0,0 +1,22 @@ +error: calling `.extend(_.chars())` + --> $DIR/string_extend.rs:18:5 + | +LL | s.extend(abc.chars()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(abc)` + | + = note: `-D clippy::string-extend-chars` implied by `-D warnings` + +error: calling `.extend(_.chars())` + --> $DIR/string_extend.rs:21:5 + | +LL | s.extend("abc".chars()); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str("abc")` + +error: calling `.extend(_.chars())` + --> $DIR/string_extend.rs:24:5 + | +LL | s.extend(def.chars()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.push_str(&def)` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/string_lit_as_bytes.fixed b/src/tools/clippy/tests/ui/string_lit_as_bytes.fixed new file mode 100644 index 0000000000000..7ad272ade5f9c --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_as_bytes.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::string_lit_as_bytes)] + +fn str_lit_as_bytes() { + let bs = b"hello there"; + + let bs = br###"raw string with 3# plus " ""###; + + // no warning, because these cannot be written as byte string literals: + let ubs = "☃".as_bytes(); + let ubs = "hello there! this is a very long string".as_bytes(); + + let strify = stringify!(foobar).as_bytes(); + + let includestr = include_bytes!("entry_unfixable.rs"); + + let _ = b"string with newline\t\n"; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/string_lit_as_bytes.rs b/src/tools/clippy/tests/ui/string_lit_as_bytes.rs new file mode 100644 index 0000000000000..1bf4538b7c94f --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_as_bytes.rs @@ -0,0 +1,22 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::string_lit_as_bytes)] + +fn str_lit_as_bytes() { + let bs = "hello there".as_bytes(); + + let bs = r###"raw string with 3# plus " ""###.as_bytes(); + + // no warning, because these cannot be written as byte string literals: + let ubs = "☃".as_bytes(); + let ubs = "hello there! this is a very long string".as_bytes(); + + let strify = stringify!(foobar).as_bytes(); + + let includestr = include_str!("entry_unfixable.rs").as_bytes(); + + let _ = "string with newline\t\n".as_bytes(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/string_lit_as_bytes.stderr b/src/tools/clippy/tests/ui/string_lit_as_bytes.stderr new file mode 100644 index 0000000000000..ff6e3346dfc75 --- /dev/null +++ b/src/tools/clippy/tests/ui/string_lit_as_bytes.stderr @@ -0,0 +1,28 @@ +error: calling `as_bytes()` on a string literal + --> $DIR/string_lit_as_bytes.rs:7:14 + | +LL | let bs = "hello there".as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `b"hello there"` + | + = note: `-D clippy::string-lit-as-bytes` implied by `-D warnings` + +error: calling `as_bytes()` on a string literal + --> $DIR/string_lit_as_bytes.rs:9:14 + | +LL | let bs = r###"raw string with 3# plus " ""###.as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `br###"raw string with 3# plus " ""###` + +error: calling `as_bytes()` on `include_str!(..)` + --> $DIR/string_lit_as_bytes.rs:17:22 + | +LL | let includestr = include_str!("entry_unfixable.rs").as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `include_bytes!(..)` instead: `include_bytes!("entry_unfixable.rs")` + +error: calling `as_bytes()` on a string literal + --> $DIR/string_lit_as_bytes.rs:19:13 + | +LL | let _ = "string with newline/t/n".as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `b"string with newline/t/n"` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/struct_excessive_bools.rs b/src/tools/clippy/tests/ui/struct_excessive_bools.rs new file mode 100644 index 0000000000000..ce4fe830a0a21 --- /dev/null +++ b/src/tools/clippy/tests/ui/struct_excessive_bools.rs @@ -0,0 +1,44 @@ +#![warn(clippy::struct_excessive_bools)] + +macro_rules! foo { + () => { + struct MacroFoo { + a: bool, + b: bool, + c: bool, + d: bool, + } + }; +} + +foo!(); + +struct Foo { + a: bool, + b: bool, + c: bool, +} + +struct BadFoo { + a: bool, + b: bool, + c: bool, + d: bool, +} + +#[repr(C)] +struct Bar { + a: bool, + b: bool, + c: bool, + d: bool, +} + +fn main() { + struct FooFoo { + a: bool, + b: bool, + c: bool, + d: bool, + } +} diff --git a/src/tools/clippy/tests/ui/struct_excessive_bools.stderr b/src/tools/clippy/tests/ui/struct_excessive_bools.stderr new file mode 100644 index 0000000000000..2941bf2983aa8 --- /dev/null +++ b/src/tools/clippy/tests/ui/struct_excessive_bools.stderr @@ -0,0 +1,29 @@ +error: more than 3 bools in a struct + --> $DIR/struct_excessive_bools.rs:22:1 + | +LL | / struct BadFoo { +LL | | a: bool, +LL | | b: bool, +LL | | c: bool, +LL | | d: bool, +LL | | } + | |_^ + | + = note: `-D clippy::struct-excessive-bools` implied by `-D warnings` + = help: consider using a state machine or refactoring bools into two-variant enums + +error: more than 3 bools in a struct + --> $DIR/struct_excessive_bools.rs:38:5 + | +LL | / struct FooFoo { +LL | | a: bool, +LL | | b: bool, +LL | | c: bool, +LL | | d: bool, +LL | | } + | |_____^ + | + = help: consider using a state machine or refactoring bools into two-variant enums + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.rs b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.rs new file mode 100644 index 0000000000000..1f5b981188706 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.rs @@ -0,0 +1,90 @@ +#![warn(clippy::suspicious_arithmetic_impl)] +use std::ops::{Add, AddAssign, BitOrAssign, Div, DivAssign, Mul, MulAssign, Sub}; + +#[derive(Copy, Clone)] +struct Foo(u32); + +impl Add for Foo { + type Output = Foo; + + fn add(self, other: Self) -> Self { + Foo(self.0 - other.0) + } +} + +impl AddAssign for Foo { + fn add_assign(&mut self, other: Foo) { + *self = *self - other; + } +} + +impl BitOrAssign for Foo { + fn bitor_assign(&mut self, other: Foo) { + let idx = other.0; + self.0 |= 1 << idx; // OK: BinOpKind::Shl part of AssignOp as child node + } +} + +impl MulAssign for Foo { + fn mul_assign(&mut self, other: Foo) { + self.0 /= other.0; + } +} + +impl DivAssign for Foo { + fn div_assign(&mut self, other: Foo) { + self.0 /= other.0; // OK: BinOpKind::Div == DivAssign + } +} + +impl Mul for Foo { + type Output = Foo; + + fn mul(self, other: Foo) -> Foo { + Foo(self.0 * other.0 % 42) // OK: BinOpKind::Rem part of BiExpr as parent node + } +} + +impl Sub for Foo { + type Output = Foo; + + fn sub(self, other: Self) -> Self { + Foo(self.0 * other.0 - 42) // OK: BinOpKind::Mul part of BiExpr as child node + } +} + +impl Div for Foo { + type Output = Foo; + + fn div(self, other: Self) -> Self { + Foo(do_nothing(self.0 + other.0) / 42) // OK: BinOpKind::Add part of BiExpr as child node + } +} + +struct Bar(i32); + +impl Add for Bar { + type Output = Bar; + + fn add(self, other: Self) -> Self { + Bar(self.0 & !other.0) // OK: UnNot part of BiExpr as child node + } +} + +impl Sub for Bar { + type Output = Bar; + + fn sub(self, other: Self) -> Self { + if self.0 <= other.0 { + Bar(-(self.0 & other.0)) // OK: UnNeg part of BiExpr as parent node + } else { + Bar(0) + } + } +} + +fn main() {} + +fn do_nothing(x: u32) -> u32 { + x +} diff --git a/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.stderr b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.stderr new file mode 100644 index 0000000000000..7e42d72c30b2c --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_arithmetic_impl.stderr @@ -0,0 +1,24 @@ +error: Suspicious use of binary operator in `Add` impl + --> $DIR/suspicious_arithmetic_impl.rs:11:20 + | +LL | Foo(self.0 - other.0) + | ^ + | + = note: `-D clippy::suspicious-arithmetic-impl` implied by `-D warnings` + +error: Suspicious use of binary operator in `AddAssign` impl + --> $DIR/suspicious_arithmetic_impl.rs:17:23 + | +LL | *self = *self - other; + | ^ + | + = note: `#[deny(clippy::suspicious_op_assign_impl)]` on by default + +error: Suspicious use of binary operator in `MulAssign` impl + --> $DIR/suspicious_arithmetic_impl.rs:30:16 + | +LL | self.0 /= other.0; + | ^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_map.rs b/src/tools/clippy/tests/ui/suspicious_map.rs new file mode 100644 index 0000000000000..d838d8fde2105 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_map.rs @@ -0,0 +1,5 @@ +#![warn(clippy::suspicious_map)] + +fn main() { + let _ = (0..3).map(|x| x + 2).count(); +} diff --git a/src/tools/clippy/tests/ui/suspicious_map.stderr b/src/tools/clippy/tests/ui/suspicious_map.stderr new file mode 100644 index 0000000000000..e1b4ba40376f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_map.stderr @@ -0,0 +1,11 @@ +error: this call to `map()` won't have an effect on the call to `count()` + --> $DIR/suspicious_map.rs:4:13 + | +LL | let _ = (0..3).map(|x| x + 2).count(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::suspicious-map` implied by `-D warnings` + = help: make sure you did not confuse `map` with `filter` or `for_each` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.rs b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.rs new file mode 100644 index 0000000000000..9564e373c246d --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.rs @@ -0,0 +1,23 @@ +#![warn(clippy::suspicious_unary_op_formatting)] + +#[rustfmt::skip] +fn main() { + // weird binary operator formatting: + let a = 42; + + if a >- 30 {} + if a >=- 30 {} + + let b = true; + let c = false; + + if b &&! c {} + + if a >- 30 {} + + // those are ok: + if a >-30 {} + if a < -30 {} + if b && !c {} + if a > - 30 {} +} diff --git a/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.stderr b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.stderr new file mode 100644 index 0000000000000..581527dcff8e4 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_unary_op_formatting.stderr @@ -0,0 +1,35 @@ +error: by not having a space between `>` and `-` it looks like `>-` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:8:9 + | +LL | if a >- 30 {} + | ^^^^ + | + = note: `-D clippy::suspicious-unary-op-formatting` implied by `-D warnings` + = help: put a space between `>` and `-` and remove the space after `-` + +error: by not having a space between `>=` and `-` it looks like `>=-` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:9:9 + | +LL | if a >=- 30 {} + | ^^^^^ + | + = help: put a space between `>=` and `-` and remove the space after `-` + +error: by not having a space between `&&` and `!` it looks like `&&!` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:14:9 + | +LL | if b &&! c {} + | ^^^^^ + | + = help: put a space between `&&` and `!` and remove the space after `!` + +error: by not having a space between `>` and `-` it looks like `>-` is a single operator + --> $DIR/suspicious_unary_op_formatting.rs:16:9 + | +LL | if a >- 30 {} + | ^^^^^^ + | + = help: put a space between `>` and `-` and remove the space after `-` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/swap.fixed b/src/tools/clippy/tests/ui/swap.fixed new file mode 100644 index 0000000000000..0f8f839a0d542 --- /dev/null +++ b/src/tools/clippy/tests/ui/swap.fixed @@ -0,0 +1,83 @@ +// run-rustfix + +#![warn(clippy::all)] +#![allow( + clippy::blacklisted_name, + clippy::no_effect, + clippy::redundant_clone, + redundant_semicolons, + unused_assignments +)] + +struct Foo(u32); + +#[derive(Clone)] +struct Bar { + a: u32, + b: u32, +} + +fn field() { + let mut bar = Bar { a: 1, b: 2 }; + + let temp = bar.a; + bar.a = bar.b; + bar.b = temp; + + let mut baz = vec![bar.clone(), bar.clone()]; + let temp = baz[0].a; + baz[0].a = baz[1].a; + baz[1].a = temp; +} + +fn array() { + let mut foo = [1, 2]; + foo.swap(0, 1); + + foo.swap(0, 1); +} + +fn slice() { + let foo = &mut [1, 2]; + foo.swap(0, 1); + + foo.swap(0, 1); +} + +fn unswappable_slice() { + let foo = &mut [vec![1, 2], vec![3, 4]]; + let temp = foo[0][1]; + foo[0][1] = foo[1][0]; + foo[1][0] = temp; + + // swap(foo[0][1], foo[1][0]) would fail +} + +fn vec() { + let mut foo = vec![1, 2]; + foo.swap(0, 1); + + foo.swap(0, 1); +} + +#[rustfmt::skip] +fn main() { + field(); + array(); + slice(); + unswappable_slice(); + vec(); + + let mut a = 42; + let mut b = 1337; + + std::mem::swap(&mut a, &mut b); + + ; std::mem::swap(&mut a, &mut b); + + let mut c = Foo(42); + + std::mem::swap(&mut c.0, &mut a); + + ; std::mem::swap(&mut c.0, &mut a); +} diff --git a/src/tools/clippy/tests/ui/swap.rs b/src/tools/clippy/tests/ui/swap.rs new file mode 100644 index 0000000000000..5763d9e82d486 --- /dev/null +++ b/src/tools/clippy/tests/ui/swap.rs @@ -0,0 +1,95 @@ +// run-rustfix + +#![warn(clippy::all)] +#![allow( + clippy::blacklisted_name, + clippy::no_effect, + clippy::redundant_clone, + redundant_semicolons, + unused_assignments +)] + +struct Foo(u32); + +#[derive(Clone)] +struct Bar { + a: u32, + b: u32, +} + +fn field() { + let mut bar = Bar { a: 1, b: 2 }; + + let temp = bar.a; + bar.a = bar.b; + bar.b = temp; + + let mut baz = vec![bar.clone(), bar.clone()]; + let temp = baz[0].a; + baz[0].a = baz[1].a; + baz[1].a = temp; +} + +fn array() { + let mut foo = [1, 2]; + let temp = foo[0]; + foo[0] = foo[1]; + foo[1] = temp; + + foo.swap(0, 1); +} + +fn slice() { + let foo = &mut [1, 2]; + let temp = foo[0]; + foo[0] = foo[1]; + foo[1] = temp; + + foo.swap(0, 1); +} + +fn unswappable_slice() { + let foo = &mut [vec![1, 2], vec![3, 4]]; + let temp = foo[0][1]; + foo[0][1] = foo[1][0]; + foo[1][0] = temp; + + // swap(foo[0][1], foo[1][0]) would fail +} + +fn vec() { + let mut foo = vec![1, 2]; + let temp = foo[0]; + foo[0] = foo[1]; + foo[1] = temp; + + foo.swap(0, 1); +} + +#[rustfmt::skip] +fn main() { + field(); + array(); + slice(); + unswappable_slice(); + vec(); + + let mut a = 42; + let mut b = 1337; + + a = b; + b = a; + + ; let t = a; + a = b; + b = t; + + let mut c = Foo(42); + + c.0 = a; + a = c.0; + + ; let t = c.0; + c.0 = a; + a = t; +} diff --git a/src/tools/clippy/tests/ui/swap.stderr b/src/tools/clippy/tests/ui/swap.stderr new file mode 100644 index 0000000000000..f49bcfedf3a19 --- /dev/null +++ b/src/tools/clippy/tests/ui/swap.stderr @@ -0,0 +1,69 @@ +error: this looks like you are swapping elements of `foo` manually + --> $DIR/swap.rs:35:5 + | +LL | / let temp = foo[0]; +LL | | foo[0] = foo[1]; +LL | | foo[1] = temp; + | |_________________^ help: try: `foo.swap(0, 1)` + | + = note: `-D clippy::manual-swap` implied by `-D warnings` + +error: this looks like you are swapping elements of `foo` manually + --> $DIR/swap.rs:44:5 + | +LL | / let temp = foo[0]; +LL | | foo[0] = foo[1]; +LL | | foo[1] = temp; + | |_________________^ help: try: `foo.swap(0, 1)` + +error: this looks like you are swapping elements of `foo` manually + --> $DIR/swap.rs:62:5 + | +LL | / let temp = foo[0]; +LL | | foo[0] = foo[1]; +LL | | foo[1] = temp; + | |_________________^ help: try: `foo.swap(0, 1)` + +error: this looks like you are swapping `a` and `b` manually + --> $DIR/swap.rs:83:7 + | +LL | ; let t = a; + | _______^ +LL | | a = b; +LL | | b = t; + | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are swapping `c.0` and `a` manually + --> $DIR/swap.rs:92:7 + | +LL | ; let t = c.0; + | _______^ +LL | | c.0 = a; +LL | | a = t; + | |_________^ help: try: `std::mem::swap(&mut c.0, &mut a)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `a` and `b` + --> $DIR/swap.rs:80:5 + | +LL | / a = b; +LL | | b = a; + | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: `-D clippy::almost-swapped` implied by `-D warnings` + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `c.0` and `a` + --> $DIR/swap.rs:89:5 + | +LL | / c.0 = a; +LL | | a = c.0; + | |___________^ help: try: `std::mem::swap(&mut c.0, &mut a)` + | + = note: or maybe you should use `std::mem::replace`? + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.fixed b/src/tools/clippy/tests/ui/tabs_in_doc_comments.fixed new file mode 100644 index 0000000000000..4bc4bc86c76c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +#![warn(clippy::tabs_in_doc_comments)] +#[allow(dead_code)] + +/// +/// Struct to hold two strings: +/// - first one +/// - second one +pub struct DoubleString { + /// + /// - First String: + /// - needs to be inside here + first_string: String, + /// + /// - Second String: + /// - needs to be inside here + second_string: String, +} + +/// This is main +fn main() {} diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.rs b/src/tools/clippy/tests/ui/tabs_in_doc_comments.rs new file mode 100644 index 0000000000000..9db3416e65964 --- /dev/null +++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.rs @@ -0,0 +1,22 @@ +// run-rustfix + +#![warn(clippy::tabs_in_doc_comments)] +#[allow(dead_code)] + +/// +/// Struct to hold two strings: +/// - first one +/// - second one +pub struct DoubleString { + /// + /// - First String: + /// - needs to be inside here + first_string: String, + /// + /// - Second String: + /// - needs to be inside here + second_string: String, +} + +/// This is main +fn main() {} diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr b/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr new file mode 100644 index 0000000000000..355f2e8057964 --- /dev/null +++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr @@ -0,0 +1,52 @@ +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:12:9 + | +LL | /// - First String: + | ^^^^ help: consider using four spaces per tab + | + = note: `-D clippy::tabs-in-doc-comments` implied by `-D warnings` + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:13:9 + | +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:16:9 + | +LL | /// - Second String: + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:17:9 + | +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:8:5 + | +LL | /// - first one + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:8:13 + | +LL | /// - first one + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:9:5 + | +LL | /// - second one + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:9:14 + | +LL | /// - second one + | ^^^^ help: consider using four spaces per tab + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/temporary_assignment.rs b/src/tools/clippy/tests/ui/temporary_assignment.rs new file mode 100644 index 0000000000000..c6c315d5fab5d --- /dev/null +++ b/src/tools/clippy/tests/ui/temporary_assignment.rs @@ -0,0 +1,75 @@ +#![warn(clippy::temporary_assignment)] + +use std::ops::{Deref, DerefMut}; + +struct TupleStruct(i32); + +struct Struct { + field: i32, +} + +struct MultiStruct { + structure: Struct, +} + +struct Wrapper<'a> { + inner: &'a mut Struct, +} + +impl<'a> Deref for Wrapper<'a> { + type Target = Struct; + fn deref(&self) -> &Struct { + self.inner + } +} + +impl<'a> DerefMut for Wrapper<'a> { + fn deref_mut(&mut self) -> &mut Struct { + self.inner + } +} + +struct ArrayStruct { + array: [i32; 1], +} + +const A: TupleStruct = TupleStruct(1); +const B: Struct = Struct { field: 1 }; +const C: MultiStruct = MultiStruct { + structure: Struct { field: 1 }, +}; +const D: ArrayStruct = ArrayStruct { array: [1] }; + +fn main() { + let mut s = Struct { field: 0 }; + let mut t = (0, 0); + + Struct { field: 0 }.field = 1; + MultiStruct { + structure: Struct { field: 0 }, + } + .structure + .field = 1; + ArrayStruct { array: [0] }.array[0] = 1; + (0, 0).0 = 1; + + A.0 = 2; + B.field = 2; + C.structure.field = 2; + D.array[0] = 2; + + // no error + s.field = 1; + t.0 = 1; + Wrapper { inner: &mut s }.field = 1; + let mut a_mut = TupleStruct(1); + a_mut.0 = 2; + let mut b_mut = Struct { field: 1 }; + b_mut.field = 2; + let mut c_mut = MultiStruct { + structure: Struct { field: 1 }, + }; + c_mut.structure.field = 2; + let mut d_mut = ArrayStruct { array: [1] }; + d_mut.array[0] = 2; +} diff --git a/src/tools/clippy/tests/ui/temporary_assignment.stderr b/src/tools/clippy/tests/ui/temporary_assignment.stderr new file mode 100644 index 0000000000000..4efe2d4bb6713 --- /dev/null +++ b/src/tools/clippy/tests/ui/temporary_assignment.stderr @@ -0,0 +1,56 @@ +error: assignment to temporary + --> $DIR/temporary_assignment.rs:47:5 + | +LL | Struct { field: 0 }.field = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::temporary-assignment` implied by `-D warnings` + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:48:5 + | +LL | / MultiStruct { +LL | | structure: Struct { field: 0 }, +LL | | } +LL | | .structure +LL | | .field = 1; + | |______________^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:53:5 + | +LL | ArrayStruct { array: [0] }.array[0] = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:54:5 + | +LL | (0, 0).0 = 1; + | ^^^^^^^^^^^^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:56:5 + | +LL | A.0 = 2; + | ^^^^^^^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:57:5 + | +LL | B.field = 2; + | ^^^^^^^^^^^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:58:5 + | +LL | C.structure.field = 2; + | ^^^^^^^^^^^^^^^^^^^^^ + +error: assignment to temporary + --> $DIR/temporary_assignment.rs:59:5 + | +LL | D.array[0] = 2; + | ^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/to_digit_is_some.fixed b/src/tools/clippy/tests/ui/to_digit_is_some.fixed new file mode 100644 index 0000000000000..19184df0becb5 --- /dev/null +++ b/src/tools/clippy/tests/ui/to_digit_is_some.fixed @@ -0,0 +1,11 @@ +//run-rustfix + +#![warn(clippy::to_digit_is_some)] + +fn main() { + let c = 'x'; + let d = &c; + + let _ = d.is_digit(10); + let _ = char::is_digit(c, 10); +} diff --git a/src/tools/clippy/tests/ui/to_digit_is_some.rs b/src/tools/clippy/tests/ui/to_digit_is_some.rs new file mode 100644 index 0000000000000..45a6728ebf578 --- /dev/null +++ b/src/tools/clippy/tests/ui/to_digit_is_some.rs @@ -0,0 +1,11 @@ +//run-rustfix + +#![warn(clippy::to_digit_is_some)] + +fn main() { + let c = 'x'; + let d = &c; + + let _ = d.to_digit(10).is_some(); + let _ = char::to_digit(c, 10).is_some(); +} diff --git a/src/tools/clippy/tests/ui/to_digit_is_some.stderr b/src/tools/clippy/tests/ui/to_digit_is_some.stderr new file mode 100644 index 0000000000000..177d3ccd3e23d --- /dev/null +++ b/src/tools/clippy/tests/ui/to_digit_is_some.stderr @@ -0,0 +1,16 @@ +error: use of `.to_digit(..).is_some()` + --> $DIR/to_digit_is_some.rs:9:13 + | +LL | let _ = d.to_digit(10).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `d.is_digit(10)` + | + = note: `-D clippy::to-digit-is-some` implied by `-D warnings` + +error: use of `.to_digit(..).is_some()` + --> $DIR/to_digit_is_some.rs:10:13 + | +LL | let _ = char::to_digit(c, 10).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `char::is_digit(c, 10)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg.fixed b/src/tools/clippy/tests/ui/toplevel_ref_arg.fixed new file mode 100644 index 0000000000000..33605aca0199c --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg.fixed @@ -0,0 +1,29 @@ +// run-rustfix + +#![warn(clippy::toplevel_ref_arg)] + +fn main() { + // Closures should not warn + let y = |ref x| println!("{:?}", x); + y(1u8); + + let _x = &1; + + let _y: &(&_, u8) = &(&1, 2); + + let _z = &(1 + 2); + + let _z = &mut (1 + 2); + + let (ref x, _) = (1, 2); // ok, not top level + println!("The answer is {}.", x); + + let _x = &vec![1, 2, 3]; + + // Make sure that allowing the lint works + #[allow(clippy::toplevel_ref_arg)] + let ref mut _x = 1_234_543; + + // ok + for ref _x in 0..10 {} +} diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg.rs b/src/tools/clippy/tests/ui/toplevel_ref_arg.rs new file mode 100644 index 0000000000000..59759f1189333 --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg.rs @@ -0,0 +1,29 @@ +// run-rustfix + +#![warn(clippy::toplevel_ref_arg)] + +fn main() { + // Closures should not warn + let y = |ref x| println!("{:?}", x); + y(1u8); + + let ref _x = 1; + + let ref _y: (&_, u8) = (&1, 2); + + let ref _z = 1 + 2; + + let ref mut _z = 1 + 2; + + let (ref x, _) = (1, 2); // ok, not top level + println!("The answer is {}.", x); + + let ref _x = vec![1, 2, 3]; + + // Make sure that allowing the lint works + #[allow(clippy::toplevel_ref_arg)] + let ref mut _x = 1_234_543; + + // ok + for ref _x in 0..10 {} +} diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg.stderr b/src/tools/clippy/tests/ui/toplevel_ref_arg.stderr new file mode 100644 index 0000000000000..19d69496709be --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg.stderr @@ -0,0 +1,34 @@ +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:10:9 + | +LL | let ref _x = 1; + | ----^^^^^^----- help: try: `let _x = &1;` + | + = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:12:9 + | +LL | let ref _y: (&_, u8) = (&1, 2); + | ----^^^^^^--------------------- help: try: `let _y: &(&_, u8) = &(&1, 2);` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:14:9 + | +LL | let ref _z = 1 + 2; + | ----^^^^^^--------- help: try: `let _z = &(1 + 2);` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:16:9 + | +LL | let ref mut _z = 1 + 2; + | ----^^^^^^^^^^--------- help: try: `let _z = &mut (1 + 2);` + +error: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead + --> $DIR/toplevel_ref_arg.rs:21:9 + | +LL | let ref _x = vec![1, 2, 3]; + | ----^^^^^^----------------- help: try: `let _x = &vec![1, 2, 3];` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.rs b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.rs new file mode 100644 index 0000000000000..42cac2ba4de25 --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.rs @@ -0,0 +1,11 @@ +#![warn(clippy::toplevel_ref_arg)] +#![allow(unused)] + +fn the_answer(ref mut x: u8) { + *x = 42; +} + +fn main() { + let mut x = 0; + the_answer(x); +} diff --git a/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr new file mode 100644 index 0000000000000..295e2f35608bf --- /dev/null +++ b/src/tools/clippy/tests/ui/toplevel_ref_arg_non_rustfix.stderr @@ -0,0 +1,10 @@ +error: `ref` directly on a function argument is ignored. Consider using a reference type instead. + --> $DIR/toplevel_ref_arg_non_rustfix.rs:4:15 + | +LL | fn the_answer(ref mut x: u8) { + | ^^^^^^^^^ + | + = note: `-D clippy::toplevel-ref-arg` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/trailing_zeros.rs b/src/tools/clippy/tests/ui/trailing_zeros.rs new file mode 100644 index 0000000000000..1cef8c2cfc997 --- /dev/null +++ b/src/tools/clippy/tests/ui/trailing_zeros.rs @@ -0,0 +1,9 @@ +#![allow(unused_parens)] + +fn main() { + let x: i32 = 42; + let _ = (x & 0b1111 == 0); // suggest trailing_zeros + let _ = x & 0b1_1111 == 0; // suggest trailing_zeros + let _ = x & 0b1_1010 == 0; // do not lint + let _ = x & 1 == 0; // do not lint +} diff --git a/src/tools/clippy/tests/ui/trailing_zeros.stderr b/src/tools/clippy/tests/ui/trailing_zeros.stderr new file mode 100644 index 0000000000000..320d9cc3f6434 --- /dev/null +++ b/src/tools/clippy/tests/ui/trailing_zeros.stderr @@ -0,0 +1,16 @@ +error: bit mask could be simplified with a call to `trailing_zeros` + --> $DIR/trailing_zeros.rs:5:13 + | +LL | let _ = (x & 0b1111 == 0); // suggest trailing_zeros + | ^^^^^^^^^^^^^^^^^ help: try: `x.trailing_zeros() >= 4` + | + = note: `-D clippy::verbose-bit-mask` implied by `-D warnings` + +error: bit mask could be simplified with a call to `trailing_zeros` + --> $DIR/trailing_zeros.rs:6:13 + | +LL | let _ = x & 0b1_1111 == 0; // suggest trailing_zeros + | ^^^^^^^^^^^^^^^^^ help: try: `x.trailing_zeros() >= 5` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute.rs b/src/tools/clippy/tests/ui/transmute.rs new file mode 100644 index 0000000000000..bb853d237047f --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute.rs @@ -0,0 +1,94 @@ +#![allow(dead_code)] + +extern crate core; + +use std::mem::transmute as my_transmute; +use std::vec::Vec as MyVec; + +fn my_int() -> Usize { + Usize(42) +} + +fn my_vec() -> MyVec { + vec![] +} + +#[allow(clippy::needless_lifetimes, clippy::transmute_ptr_to_ptr)] +#[warn(clippy::useless_transmute)] +unsafe fn _generic<'a, T, U: 'a>(t: &'a T) { + let _: &'a T = core::intrinsics::transmute(t); + + let _: &'a U = core::intrinsics::transmute(t); + + let _: *const T = core::intrinsics::transmute(t); + + let _: *mut T = core::intrinsics::transmute(t); + + let _: *const U = core::intrinsics::transmute(t); +} + +#[warn(clippy::useless_transmute)] +fn useless() { + unsafe { + let _: Vec = core::intrinsics::transmute(my_vec()); + + let _: Vec = core::mem::transmute(my_vec()); + + let _: Vec = std::intrinsics::transmute(my_vec()); + + let _: Vec = std::mem::transmute(my_vec()); + + let _: Vec = my_transmute(my_vec()); + + let _: *const usize = std::mem::transmute(5_isize); + + let _ = 5_isize as *const usize; + + let _: *const usize = std::mem::transmute(1 + 1usize); + + let _ = (1 + 1_usize) as *const usize; + } +} + +struct Usize(usize); + +#[warn(clippy::crosspointer_transmute)] +fn crosspointer() { + let mut int: Usize = Usize(0); + let int_const_ptr: *const Usize = &int as *const Usize; + let int_mut_ptr: *mut Usize = &mut int as *mut Usize; + + unsafe { + let _: Usize = core::intrinsics::transmute(int_const_ptr); + + let _: Usize = core::intrinsics::transmute(int_mut_ptr); + + let _: *const Usize = core::intrinsics::transmute(my_int()); + + let _: *mut Usize = core::intrinsics::transmute(my_int()); + } +} + +#[warn(clippy::transmute_int_to_char)] +fn int_to_char() { + let _: char = unsafe { std::mem::transmute(0_u32) }; + let _: char = unsafe { std::mem::transmute(0_i32) }; +} + +#[warn(clippy::transmute_int_to_bool)] +fn int_to_bool() { + let _: bool = unsafe { std::mem::transmute(0_u8) }; +} + +#[warn(clippy::transmute_int_to_float)] +fn int_to_float() { + let _: f32 = unsafe { std::mem::transmute(0_u32) }; + let _: f32 = unsafe { std::mem::transmute(0_i32) }; +} + +fn bytes_to_str(b: &[u8], mb: &mut [u8]) { + let _: &str = unsafe { std::mem::transmute(b) }; + let _: &mut str = unsafe { std::mem::transmute(mb) }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute.stderr b/src/tools/clippy/tests/ui/transmute.stderr new file mode 100644 index 0000000000000..8582080498f3e --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute.stderr @@ -0,0 +1,146 @@ +error: transmute from a type (`&T`) to itself + --> $DIR/transmute.rs:19:20 + | +LL | let _: &'a T = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::useless-transmute` implied by `-D warnings` + +error: transmute from a reference to a pointer + --> $DIR/transmute.rs:23:23 + | +LL | let _: *const T = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T` + +error: transmute from a reference to a pointer + --> $DIR/transmute.rs:25:21 + | +LL | let _: *mut T = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T` + +error: transmute from a reference to a pointer + --> $DIR/transmute.rs:27:23 + | +LL | let _: *const U = core::intrinsics::transmute(t); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U` + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:33:27 + | +LL | let _: Vec = core::intrinsics::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:35:27 + | +LL | let _: Vec = core::mem::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:37:27 + | +LL | let _: Vec = std::intrinsics::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:39:27 + | +LL | let _: Vec = std::mem::transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`std::vec::Vec`) to itself + --> $DIR/transmute.rs:41:27 + | +LL | let _: Vec = my_transmute(my_vec()); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from an integer to a pointer + --> $DIR/transmute.rs:43:31 + | +LL | let _: *const usize = std::mem::transmute(5_isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize` + +error: transmute from an integer to a pointer + --> $DIR/transmute.rs:47:31 + | +LL | let _: *const usize = std::mem::transmute(1 + 1usize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize` + +error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`) + --> $DIR/transmute.rs:62:24 + | +LL | let _: Usize = core::intrinsics::transmute(int_const_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::crosspointer-transmute` implied by `-D warnings` + +error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`) + --> $DIR/transmute.rs:64:24 + | +LL | let _: Usize = core::intrinsics::transmute(int_mut_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`) + --> $DIR/transmute.rs:66:31 + | +LL | let _: *const Usize = core::intrinsics::transmute(my_int()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`) + --> $DIR/transmute.rs:68:29 + | +LL | let _: *mut Usize = core::intrinsics::transmute(my_int()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a `u32` to a `char` + --> $DIR/transmute.rs:74:28 + | +LL | let _: char = unsafe { std::mem::transmute(0_u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_u32).unwrap()` + | + = note: `-D clippy::transmute-int-to-char` implied by `-D warnings` + +error: transmute from a `i32` to a `char` + --> $DIR/transmute.rs:75:28 + | +LL | let _: char = unsafe { std::mem::transmute(0_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::char::from_u32(0_i32 as u32).unwrap()` + +error: transmute from a `u8` to a `bool` + --> $DIR/transmute.rs:80:28 + | +LL | let _: bool = unsafe { std::mem::transmute(0_u8) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0` + | + = note: `-D clippy::transmute-int-to-bool` implied by `-D warnings` + +error: transmute from a `u32` to a `f32` + --> $DIR/transmute.rs:85:27 + | +LL | let _: f32 = unsafe { std::mem::transmute(0_u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_u32)` + | + = note: `-D clippy::transmute-int-to-float` implied by `-D warnings` + +error: transmute from a `i32` to a `f32` + --> $DIR/transmute.rs:86:27 + | +LL | let _: f32 = unsafe { std::mem::transmute(0_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f32::from_bits(0_i32 as u32)` + +error: transmute from a `&[u8]` to a `&str` + --> $DIR/transmute.rs:90:28 + | +LL | let _: &str = unsafe { std::mem::transmute(b) }; + | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(b).unwrap()` + | + = note: `-D clippy::transmute-bytes-to-str` implied by `-D warnings` + +error: transmute from a `&mut [u8]` to a `&mut str` + --> $DIR/transmute.rs:91:32 + | +LL | let _: &mut str = unsafe { std::mem::transmute(mb) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()` + +error: aborting due to 22 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_32bit.rs b/src/tools/clippy/tests/ui/transmute_32bit.rs new file mode 100644 index 0000000000000..ffe22b12f5510 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_32bit.rs @@ -0,0 +1,14 @@ +// ignore-64bit + +#[warn(clippy::wrong_transmute)] +fn main() { + unsafe { + let _: *const usize = std::mem::transmute(6.0f32); + + let _: *mut usize = std::mem::transmute(6.0f32); + + let _: *const usize = std::mem::transmute('x'); + + let _: *mut usize = std::mem::transmute('x'); + } +} diff --git a/src/tools/clippy/tests/ui/transmute_32bit.stderr b/src/tools/clippy/tests/ui/transmute_32bit.stderr new file mode 100644 index 0000000000000..040519564b94c --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_32bit.stderr @@ -0,0 +1,28 @@ +error: transmute from a `f32` to a pointer + --> $DIR/transmute_32bit.rs:6:31 + | +LL | let _: *const usize = std::mem::transmute(6.0f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::wrong-transmute` implied by `-D warnings` + +error: transmute from a `f32` to a pointer + --> $DIR/transmute_32bit.rs:8:29 + | +LL | let _: *mut usize = std::mem::transmute(6.0f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a `char` to a pointer + --> $DIR/transmute_32bit.rs:10:31 + | +LL | let _: *const usize = std::mem::transmute('x'); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from a `char` to a pointer + --> $DIR/transmute_32bit.rs:12:29 + | +LL | let _: *mut usize = std::mem::transmute('x'); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_64bit.rs b/src/tools/clippy/tests/ui/transmute_64bit.rs new file mode 100644 index 0000000000000..00dc0b2c36081 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_64bit.rs @@ -0,0 +1,10 @@ +// ignore-32bit + +#[warn(clippy::wrong_transmute)] +fn main() { + unsafe { + let _: *const usize = std::mem::transmute(6.0f64); + + let _: *mut usize = std::mem::transmute(6.0f64); + } +} diff --git a/src/tools/clippy/tests/ui/transmute_64bit.stderr b/src/tools/clippy/tests/ui/transmute_64bit.stderr new file mode 100644 index 0000000000000..d1854c009ef56 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_64bit.stderr @@ -0,0 +1,16 @@ +error: transmute from a `f64` to a pointer + --> $DIR/transmute_64bit.rs:6:31 + | +LL | let _: *const usize = std::mem::transmute(6.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::wrong-transmute` implied by `-D warnings` + +error: transmute from a `f64` to a pointer + --> $DIR/transmute_64bit.rs:8:29 + | +LL | let _: *mut usize = std::mem::transmute(6.0f64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_collection.rs b/src/tools/clippy/tests/ui/transmute_collection.rs new file mode 100644 index 0000000000000..cd5a7127791a8 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_collection.rs @@ -0,0 +1,47 @@ +#![warn(clippy::unsound_collection_transmute)] + +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque}; +use std::mem::transmute; + +fn main() { + unsafe { + // wrong size + let _ = transmute::<_, Vec>(vec![0u8]); + // wrong layout + let _ = transmute::<_, Vec<[u8; 4]>>(vec![1234u32]); + + // wrong size + let _ = transmute::<_, VecDeque>(VecDeque::::new()); + // wrong layout + let _ = transmute::<_, VecDeque>(VecDeque::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, BinaryHeap>(BinaryHeap::::new()); + // wrong layout + let _ = transmute::<_, BinaryHeap>(BinaryHeap::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, BTreeSet>(BTreeSet::::new()); + // wrong layout + let _ = transmute::<_, BTreeSet>(BTreeSet::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, HashSet>(HashSet::::new()); + // wrong layout + let _ = transmute::<_, HashSet>(HashSet::<[u8; 4]>::new()); + + // wrong size + let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + // wrong layout + let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + let _ = transmute::<_, BTreeMap>(BTreeMap::<[u8; 4], u32>::new()); + + // wrong size + let _ = transmute::<_, HashMap>(HashMap::::new()); + let _ = transmute::<_, HashMap>(HashMap::::new()); + // wrong layout + let _ = transmute::<_, HashMap>(HashMap::::new()); + let _ = transmute::<_, HashMap>(HashMap::<[u8; 4], u32>::new()); + } +} diff --git a/src/tools/clippy/tests/ui/transmute_collection.stderr b/src/tools/clippy/tests/ui/transmute_collection.stderr new file mode 100644 index 0000000000000..ebc05c402abf6 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_collection.stderr @@ -0,0 +1,112 @@ +error: transmute from `std::vec::Vec` to `std::vec::Vec` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:9:17 + | +LL | let _ = transmute::<_, Vec>(vec![0u8]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unsound-collection-transmute` implied by `-D warnings` + +error: transmute from `std::vec::Vec` to `std::vec::Vec<[u8; 4]>` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:11:17 + | +LL | let _ = transmute::<_, Vec<[u8; 4]>>(vec![1234u32]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::VecDeque` to `std::collections::VecDeque` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:14:17 + | +LL | let _ = transmute::<_, VecDeque>(VecDeque::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::VecDeque<[u8; 4]>` to `std::collections::VecDeque` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:16:17 + | +LL | let _ = transmute::<_, VecDeque>(VecDeque::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BinaryHeap` to `std::collections::BinaryHeap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:19:17 + | +LL | let _ = transmute::<_, BinaryHeap>(BinaryHeap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BinaryHeap<[u8; 4]>` to `std::collections::BinaryHeap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:21:17 + | +LL | let _ = transmute::<_, BinaryHeap>(BinaryHeap::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeSet` to `std::collections::BTreeSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:24:17 + | +LL | let _ = transmute::<_, BTreeSet>(BTreeSet::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeSet<[u8; 4]>` to `std::collections::BTreeSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:26:17 + | +LL | let _ = transmute::<_, BTreeSet>(BTreeSet::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashSet` to `std::collections::HashSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:29:17 + | +LL | let _ = transmute::<_, HashSet>(HashSet::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashSet<[u8; 4]>` to `std::collections::HashSet` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:31:17 + | +LL | let _ = transmute::<_, HashSet>(HashSet::<[u8; 4]>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:34:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:35:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:37:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::BTreeMap<[u8; 4], u32>` to `std::collections::BTreeMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:38:17 + | +LL | let _ = transmute::<_, BTreeMap>(BTreeMap::<[u8; 4], u32>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:41:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:42:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:44:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `std::collections::HashMap<[u8; 4], u32>` to `std::collections::HashMap` with mismatched layout is unsound + --> $DIR/transmute_collection.rs:45:17 + | +LL | let _ = transmute::<_, HashMap>(HashMap::<[u8; 4], u32>::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 18 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_float_to_int.rs b/src/tools/clippy/tests/ui/transmute_float_to_int.rs new file mode 100644 index 0000000000000..ce942751ada82 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_float_to_int.rs @@ -0,0 +1,12 @@ +#[warn(clippy::transmute_float_to_int)] + +fn float_to_int() { + let _: u32 = unsafe { std::mem::transmute(1f32) }; + let _: i32 = unsafe { std::mem::transmute(1f32) }; + let _: u64 = unsafe { std::mem::transmute(1f64) }; + let _: i64 = unsafe { std::mem::transmute(1f64) }; + let _: u64 = unsafe { std::mem::transmute(1.0) }; + let _: u64 = unsafe { std::mem::transmute(-1.0) }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_float_to_int.stderr b/src/tools/clippy/tests/ui/transmute_float_to_int.stderr new file mode 100644 index 0000000000000..eb786bb39f95a --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_float_to_int.stderr @@ -0,0 +1,40 @@ +error: transmute from a `f32` to a `u32` + --> $DIR/transmute_float_to_int.rs:4:27 + | +LL | let _: u32 = unsafe { std::mem::transmute(1f32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits()` + | + = note: `-D clippy::transmute-float-to-int` implied by `-D warnings` + +error: transmute from a `f32` to a `i32` + --> $DIR/transmute_float_to_int.rs:5:27 + | +LL | let _: i32 = unsafe { std::mem::transmute(1f32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f32.to_bits() as i32` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:6:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(1f64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits()` + +error: transmute from a `f64` to a `i64` + --> $DIR/transmute_float_to_int.rs:7:27 + | +LL | let _: i64 = unsafe { std::mem::transmute(1f64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1f64.to_bits() as i64` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:8:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(1.0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1.0f64.to_bits()` + +error: transmute from a `f64` to a `u64` + --> $DIR/transmute_float_to_int.rs:9:27 + | +LL | let _: u64 = unsafe { std::mem::transmute(-1.0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-1.0f64).to_bits()` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.rs b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.rs new file mode 100644 index 0000000000000..0d8a322f2b2b0 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.rs @@ -0,0 +1,54 @@ +#![warn(clippy::transmute_ptr_to_ptr)] + +// Make sure we can modify lifetimes, which is one of the recommended uses +// of transmute + +// Make sure we can do static lifetime transmutes +unsafe fn transmute_lifetime_to_static<'a, T>(t: &'a T) -> &'static T { + std::mem::transmute::<&'a T, &'static T>(t) +} + +// Make sure we can do non-static lifetime transmutes +unsafe fn transmute_lifetime<'a, 'b, T>(t: &'a T, u: &'b T) -> &'b T { + std::mem::transmute::<&'a T, &'b T>(t) +} + +struct LifetimeParam<'a> { + s: &'a str, +} + +struct GenericParam { + t: T, +} + +fn transmute_ptr_to_ptr() { + let ptr = &1u32 as *const u32; + let mut_ptr = &mut 1u32 as *mut u32; + unsafe { + // pointer-to-pointer transmutes; bad + let _: *const f32 = std::mem::transmute(ptr); + let _: *mut f32 = std::mem::transmute(mut_ptr); + // ref-ref transmutes; bad + let _: &f32 = std::mem::transmute(&1u32); + let _: &f64 = std::mem::transmute(&1f32); + // ^ this test is here because both f32 and f64 are the same TypeVariant, but they are not + // the same type + let _: &mut f32 = std::mem::transmute(&mut 1u32); + let _: &GenericParam = std::mem::transmute(&GenericParam { t: 1u32 }); + } + + // these are recommendations for solving the above; if these lint we need to update + // those suggestions + let _ = ptr as *const f32; + let _ = mut_ptr as *mut f32; + let _ = unsafe { &*(&1u32 as *const u32 as *const f32) }; + let _ = unsafe { &mut *(&mut 1u32 as *mut u32 as *mut f32) }; + + // transmute internal lifetimes, should not lint + let s = "hello world".to_owned(); + let lp = LifetimeParam { s: &s }; + let _: &LifetimeParam<'static> = unsafe { std::mem::transmute(&lp) }; + let _: &GenericParam<&LifetimeParam<'static>> = unsafe { std::mem::transmute(&GenericParam { t: &lp }) }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.stderr b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.stderr new file mode 100644 index 0000000000000..4d1b8fcc199e8 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ptr.stderr @@ -0,0 +1,40 @@ +error: transmute from a pointer to a pointer + --> $DIR/transmute_ptr_to_ptr.rs:29:29 + | +LL | let _: *const f32 = std::mem::transmute(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr as *const f32` + | + = note: `-D clippy::transmute-ptr-to-ptr` implied by `-D warnings` + +error: transmute from a pointer to a pointer + --> $DIR/transmute_ptr_to_ptr.rs:30:27 + | +LL | let _: *mut f32 = std::mem::transmute(mut_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `mut_ptr as *mut f32` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:32:23 + | +LL | let _: &f32 = std::mem::transmute(&1u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&1u32 as *const u32 as *const f32)` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:33:23 + | +LL | let _: &f64 = std::mem::transmute(&1f32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&1f32 as *const f32 as *const f64)` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:36:27 + | +LL | let _: &mut f32 = std::mem::transmute(&mut 1u32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(&mut 1u32 as *mut u32 as *mut f32)` + +error: transmute from a reference to a reference + --> $DIR/transmute_ptr_to_ptr.rs:37:37 + | +LL | let _: &GenericParam = std::mem::transmute(&GenericParam { t: 1u32 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&GenericParam { t: 1u32 } as *const GenericParam as *const GenericParam)` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs new file mode 100644 index 0000000000000..ba35c6adc4dee --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs @@ -0,0 +1,41 @@ +#![warn(clippy::transmute_ptr_to_ref)] + +unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { + let _: &T = std::mem::transmute(p); + let _: &T = &*p; + + let _: &mut T = std::mem::transmute(m); + let _: &mut T = &mut *m; + + let _: &T = std::mem::transmute(m); + let _: &T = &*m; + + let _: &mut T = std::mem::transmute(p as *mut T); + let _ = &mut *(p as *mut T); + + let _: &T = std::mem::transmute(o); + let _: &T = &*(o as *const T); + + let _: &mut T = std::mem::transmute(om); + let _: &mut T = &mut *(om as *mut T); + + let _: &T = std::mem::transmute(om); + let _: &T = &*(om as *const T); +} + +fn issue1231() { + struct Foo<'a, T> { + bar: &'a T, + } + + let raw = 42 as *const i32; + let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; + + let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; + + type Bar<'a> = &'a u8; + let raw = 42 as *const i32; + unsafe { std::mem::transmute::<_, Bar>(raw) }; +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr new file mode 100644 index 0000000000000..df0598a58cd36 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr @@ -0,0 +1,64 @@ +error: transmute from a pointer type (`*const T`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:4:17 + | +LL | let _: &T = std::mem::transmute(p); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*p` + | + = note: `-D clippy::transmute-ptr-to-ref` implied by `-D warnings` + +error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) + --> $DIR/transmute_ptr_to_ref.rs:7:21 + | +LL | let _: &mut T = std::mem::transmute(m); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *m` + +error: transmute from a pointer type (`*mut T`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:10:17 + | +LL | let _: &T = std::mem::transmute(m); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*m` + +error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) + --> $DIR/transmute_ptr_to_ref.rs:13:21 + | +LL | let _: &mut T = std::mem::transmute(p as *mut T); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(p as *mut T)` + +error: transmute from a pointer type (`*const U`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:16:17 + | +LL | let _: &T = std::mem::transmute(o); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(o as *const T)` + +error: transmute from a pointer type (`*mut U`) to a reference type (`&mut T`) + --> $DIR/transmute_ptr_to_ref.rs:19:21 + | +LL | let _: &mut T = std::mem::transmute(om); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(om as *mut T)` + +error: transmute from a pointer type (`*mut U`) to a reference type (`&T`) + --> $DIR/transmute_ptr_to_ref.rs:22:17 + | +LL | let _: &T = std::mem::transmute(om); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(om as *const T)` + +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo`) + --> $DIR/transmute_ptr_to_ref.rs:32:32 + | +LL | let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<_>)` + +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<&u8>`) + --> $DIR/transmute_ptr_to_ref.rs:34:33 + | +LL | let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<&_>)` + +error: transmute from a pointer type (`*const i32`) to a reference type (`&u8`) + --> $DIR/transmute_ptr_to_ref.rs:38:14 + | +LL | unsafe { std::mem::transmute::<_, Bar>(raw) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const u8)` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/transmuting_null.rs b/src/tools/clippy/tests/ui/transmuting_null.rs new file mode 100644 index 0000000000000..ea3ee8edc81b1 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmuting_null.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] +#![warn(clippy::transmuting_null)] +#![allow(clippy::zero_ptr)] +#![allow(clippy::transmute_ptr_to_ref)] +#![allow(clippy::eq_op)] + +// Easy to lint because these only span one line. +fn one_liners() { + unsafe { + let _: &u64 = std::mem::transmute(0 as *const u64); + let _: &u64 = std::mem::transmute(std::ptr::null::()); + } +} + +pub const ZPTR: *const usize = 0 as *const _; +pub const NOT_ZPTR: *const usize = 1 as *const _; + +fn transmute_const() { + unsafe { + // Should raise a lint. + let _: &u64 = std::mem::transmute(ZPTR); + // Should NOT raise a lint. + let _: &u64 = std::mem::transmute(NOT_ZPTR); + } +} + +fn main() { + one_liners(); + transmute_const(); +} diff --git a/src/tools/clippy/tests/ui/transmuting_null.stderr b/src/tools/clippy/tests/ui/transmuting_null.stderr new file mode 100644 index 0000000000000..05f91ee2adaa8 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmuting_null.stderr @@ -0,0 +1,22 @@ +error: transmuting a known null pointer into a reference. + --> $DIR/transmuting_null.rs:10:23 + | +LL | let _: &u64 = std::mem::transmute(0 as *const u64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::transmuting-null` implied by `-D warnings` + +error: transmuting a known null pointer into a reference. + --> $DIR/transmuting_null.rs:11:23 + | +LL | let _: &u64 = std::mem::transmute(std::ptr::null::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmuting a known null pointer into a reference. + --> $DIR/transmuting_null.rs:21:23 + | +LL | let _: &u64 = std::mem::transmute(ZPTR); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs new file mode 100644 index 0000000000000..316426f1cf181 --- /dev/null +++ b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs @@ -0,0 +1,114 @@ +// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)" +// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)" + +#![deny(clippy::trivially_copy_pass_by_ref)] +#![allow( + clippy::many_single_char_names, + clippy::blacklisted_name, + clippy::redundant_field_names +)] + +#[derive(Copy, Clone)] +struct Foo(u32); + +#[derive(Copy, Clone)] +struct Bar([u8; 24]); + +#[derive(Copy, Clone)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +struct FooRef<'a> { + foo: &'a Foo, +} + +type Baz = u32; + +fn good(a: &mut u32, b: u32, c: &Bar) {} + +fn good_return_implicit_lt_ref(foo: &Foo) -> &u32 { + &foo.0 +} + +#[allow(clippy::needless_lifetimes)] +fn good_return_explicit_lt_ref<'a>(foo: &'a Foo) -> &'a u32 { + &foo.0 +} + +fn good_return_implicit_lt_struct(foo: &Foo) -> FooRef { + FooRef { foo } +} + +#[allow(clippy::needless_lifetimes)] +fn good_return_explicit_lt_struct<'a>(foo: &'a Foo) -> FooRef<'a> { + FooRef { foo } +} + +fn bad(x: &u32, y: &Foo, z: &Baz) {} + +impl Foo { + fn good(self, a: &mut u32, b: u32, c: &Bar) {} + + fn good2(&mut self) {} + + fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + + fn bad2(x: &u32, y: &Foo, z: &Baz) {} +} + +impl AsRef for Foo { + fn as_ref(&self) -> &u32 { + &self.0 + } +} + +impl Bar { + fn good(&self, a: &mut u32, b: u32, c: &Bar) {} + + fn bad2(x: &u32, y: &Foo, z: &Baz) {} +} + +trait MyTrait { + fn trait_method(&self, _foo: &Foo); +} + +pub trait MyTrait2 { + fn trait_method2(&self, _color: &Color); +} + +impl MyTrait for Foo { + fn trait_method(&self, _foo: &Foo) { + unimplemented!() + } +} + +#[allow(unused_variables)] +mod issue3992 { + pub trait A { + #[allow(clippy::trivially_copy_pass_by_ref)] + fn a(b: &u16) {} + } + + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn c(d: &u16) {} +} + +fn main() { + let (mut foo, bar) = (Foo(0), Bar([0; 24])); + let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0); + good(&mut a, b, &c); + good_return_implicit_lt_ref(&y); + good_return_explicit_lt_ref(&y); + bad(&x, &y, &z); + foo.good(&mut a, b, &c); + foo.good2(); + foo.bad(&x, &y, &z); + Foo::bad2(&x, &y, &z); + bar.good(&mut a, b, &c); + Bar::bad2(&x, &y, &z); + foo.as_ref(); +} diff --git a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr new file mode 100644 index 0000000000000..be0914e4a7947 --- /dev/null +++ b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr @@ -0,0 +1,98 @@ +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:51:11 + | +LL | fn bad(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + | +note: the lint level is defined here + --> $DIR/trivially_copy_pass_by_ref.rs:4:9 + | +LL | #![deny(clippy::trivially_copy_pass_by_ref)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:51:20 + | +LL | fn bad(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:51:29 + | +LL | fn bad(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:12 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^^ help: consider passing by value instead: `self` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:22 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:31 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:58:40 + | +LL | fn bad(&self, x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:60:16 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:60:25 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:60:34 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:72:16 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `u32` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:72:25 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:72:34 + | +LL | fn bad2(x: &u32, y: &Foo, z: &Baz) {} + | ^^^^ help: consider passing by value instead: `Baz` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:76:34 + | +LL | fn trait_method(&self, _foo: &Foo); + | ^^^^ help: consider passing by value instead: `Foo` + +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:80:37 + | +LL | fn trait_method2(&self, _color: &Color); + | ^^^^^^ help: consider passing by value instead: `Color` + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/try_err.fixed b/src/tools/clippy/tests/ui/try_err.fixed new file mode 100644 index 0000000000000..29d9139d3e346 --- /dev/null +++ b/src/tools/clippy/tests/ui/try_err.fixed @@ -0,0 +1,106 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![deny(clippy::try_err)] + +#[macro_use] +extern crate macro_rules; + +// Tests that a simple case works +// Should flag `Err(err)?` +pub fn basic_test() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { + return Err(err); + } + Ok(0) +} + +// Tests that `.into()` is added when appropriate +pub fn into_test() -> Result { + let err: u8 = 1; + // To avoid warnings during rustfix + if true { + return Err(err.into()); + } + Ok(0) +} + +// Tests that tries in general don't trigger the error +pub fn negative_test() -> Result { + Ok(nested_error()? + 1) +} + +// Tests that `.into()` isn't added when the error type +// matches the surrounding closure's return type, even +// when it doesn't match the surrounding function's. +pub fn closure_matches_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + return Err(err); + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +// Tests that `.into()` isn't added when the error type +// doesn't match the surrounding closure's return type. +pub fn closure_into_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + return Err(err.into()); + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +fn nested_error() -> Result { + Ok(1) +} + +fn main() { + basic_test().unwrap(); + into_test().unwrap(); + negative_test().unwrap(); + closure_matches_test().unwrap(); + closure_into_test().unwrap(); + + // We don't want to lint in external macros + try_err!(); +} + +macro_rules! bar { + () => { + String::from("aasdfasdfasdfa") + }; +} + +macro_rules! foo { + () => { + bar!() + }; +} + +pub fn macro_inside(fail: bool) -> Result { + if fail { + return Err(foo!()); + } + Ok(0) +} diff --git a/src/tools/clippy/tests/ui/try_err.rs b/src/tools/clippy/tests/ui/try_err.rs new file mode 100644 index 0000000000000..5e85d091a2ae7 --- /dev/null +++ b/src/tools/clippy/tests/ui/try_err.rs @@ -0,0 +1,106 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![deny(clippy::try_err)] + +#[macro_use] +extern crate macro_rules; + +// Tests that a simple case works +// Should flag `Err(err)?` +pub fn basic_test() -> Result { + let err: i32 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(0) +} + +// Tests that `.into()` is added when appropriate +pub fn into_test() -> Result { + let err: u8 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(0) +} + +// Tests that tries in general don't trigger the error +pub fn negative_test() -> Result { + Ok(nested_error()? + 1) +} + +// Tests that `.into()` isn't added when the error type +// matches the surrounding closure's return type, even +// when it doesn't match the surrounding function's. +pub fn closure_matches_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +// Tests that `.into()` isn't added when the error type +// doesn't match the surrounding closure's return type. +pub fn closure_into_test() -> Result { + let res: Result = Some(1) + .into_iter() + .map(|i| { + let err: i8 = 1; + // To avoid warnings during rustfix + if true { + Err(err)?; + } + Ok(i) + }) + .next() + .unwrap(); + + Ok(res?) +} + +fn nested_error() -> Result { + Ok(1) +} + +fn main() { + basic_test().unwrap(); + into_test().unwrap(); + negative_test().unwrap(); + closure_matches_test().unwrap(); + closure_into_test().unwrap(); + + // We don't want to lint in external macros + try_err!(); +} + +macro_rules! bar { + () => { + String::from("aasdfasdfasdfa") + }; +} + +macro_rules! foo { + () => { + bar!() + }; +} + +pub fn macro_inside(fail: bool) -> Result { + if fail { + Err(foo!())?; + } + Ok(0) +} diff --git a/src/tools/clippy/tests/ui/try_err.stderr b/src/tools/clippy/tests/ui/try_err.stderr new file mode 100644 index 0000000000000..21e9d4048a588 --- /dev/null +++ b/src/tools/clippy/tests/ui/try_err.stderr @@ -0,0 +1,38 @@ +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:15:9 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err)` + | +note: the lint level is defined here + --> $DIR/try_err.rs:4:9 + | +LL | #![deny(clippy::try_err)] + | ^^^^^^^^^^^^^^^ + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:25:9 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err.into())` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:45:17 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err)` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:64:17 + | +LL | Err(err)?; + | ^^^^^^^^^ help: try this: `return Err(err.into())` + +error: returning an `Err(_)` with the `?` operator + --> $DIR/try_err.rs:103:9 + | +LL | Err(foo!())?; + | ^^^^^^^^^^^^ help: try this: `return Err(foo!())` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/ty_fn_sig.rs b/src/tools/clippy/tests/ui/ty_fn_sig.rs new file mode 100644 index 0000000000000..9e2753dcb18d6 --- /dev/null +++ b/src/tools/clippy/tests/ui/ty_fn_sig.rs @@ -0,0 +1,14 @@ +// Regression test + +pub fn retry(f: F) { + for _i in 0.. { + f(); + } +} + +fn main() { + for y in 0..4 { + let func = || (); + func(); + } +} diff --git a/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs b/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs new file mode 100644 index 0000000000000..8b538be762b0c --- /dev/null +++ b/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs @@ -0,0 +1,19 @@ +#[deny(clippy::type_repetition_in_bounds)] + +pub fn foo(_t: T) +where + T: Copy, + T: Clone, +{ + unimplemented!(); +} + +pub fn bar(_t: T, _u: U) +where + T: Copy, + U: Clone, +{ + unimplemented!(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr b/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr new file mode 100644 index 0000000000000..4264e2e10bf17 --- /dev/null +++ b/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr @@ -0,0 +1,15 @@ +error: this type has already been used as a bound predicate + --> $DIR/type_repetition_in_bounds.rs:6:5 + | +LL | T: Clone, + | ^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/type_repetition_in_bounds.rs:1:8 + | +LL | #[deny(clippy::type_repetition_in_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider combining the bounds: `T: Copy + Clone` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/types.fixed b/src/tools/clippy/tests/ui/types.fixed new file mode 100644 index 0000000000000..417da42edf17b --- /dev/null +++ b/src/tools/clippy/tests/ui/types.fixed @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::cast_lossless)] + +// should not warn on lossy casting in constant types +// because not supported yet +const C: i32 = 42; +const C_I64: i64 = C as i64; + +fn main() { + // should suggest i64::from(c) + let c: i32 = 42; + let c_i64: i64 = i64::from(c); +} diff --git a/src/tools/clippy/tests/ui/types.rs b/src/tools/clippy/tests/ui/types.rs new file mode 100644 index 0000000000000..b16e9e538b106 --- /dev/null +++ b/src/tools/clippy/tests/ui/types.rs @@ -0,0 +1,15 @@ +// run-rustfix + +#![allow(dead_code, unused_variables)] +#![warn(clippy::cast_lossless)] + +// should not warn on lossy casting in constant types +// because not supported yet +const C: i32 = 42; +const C_I64: i64 = C as i64; + +fn main() { + // should suggest i64::from(c) + let c: i32 = 42; + let c_i64: i64 = c as i64; +} diff --git a/src/tools/clippy/tests/ui/types.stderr b/src/tools/clippy/tests/ui/types.stderr new file mode 100644 index 0000000000000..59c3e05a1aa3c --- /dev/null +++ b/src/tools/clippy/tests/ui/types.stderr @@ -0,0 +1,10 @@ +error: casting `i32` to `i64` may become silently lossy if you later change the type + --> $DIR/types.rs:14:22 + | +LL | let c_i64: i64 = c as i64; + | ^^^^^^^^ help: try: `i64::from(c)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/unicode.rs b/src/tools/clippy/tests/ui/unicode.rs new file mode 100644 index 0000000000000..27db9594f3b33 --- /dev/null +++ b/src/tools/clippy/tests/ui/unicode.rs @@ -0,0 +1,23 @@ +#[warn(clippy::zero_width_space)] +fn zero() { + print!("Here >​< is a ZWS, and ​another"); + print!("This\u{200B}is\u{200B}fine"); +} + +#[warn(clippy::unicode_not_nfc)] +fn canon() { + print!("̀àh?"); + print!("a\u{0300}h?"); // also ok +} + +#[warn(clippy::non_ascii_literal)] +fn uni() { + print!("Üben!"); + print!("\u{DC}ben!"); // this is ok +} + +fn main() { + zero(); + uni(); + canon(); +} diff --git a/src/tools/clippy/tests/ui/unicode.stderr b/src/tools/clippy/tests/ui/unicode.stderr new file mode 100644 index 0000000000000..4575a132e5b2c --- /dev/null +++ b/src/tools/clippy/tests/ui/unicode.stderr @@ -0,0 +1,26 @@ +error: zero-width space detected + --> $DIR/unicode.rs:3:12 + | +LL | print!("Here >​< is a ZWS, and ​another"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{200B}< is a ZWS, and /u{200B}another"` + | + = note: `-D clippy::zero-width-space` implied by `-D warnings` + +error: non-NFC Unicode sequence detected + --> $DIR/unicode.rs:9:12 + | +LL | print!("̀àh?"); + | ^^^^^ help: consider replacing the string with: `"̀àh?"` + | + = note: `-D clippy::unicode-not-nfc` implied by `-D warnings` + +error: literal non-ASCII character detected + --> $DIR/unicode.rs:15:12 + | +LL | print!("Üben!"); + | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"` + | + = note: `-D clippy::non-ascii-literal` implied by `-D warnings` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/uninit.rs b/src/tools/clippy/tests/ui/uninit.rs new file mode 100644 index 0000000000000..f42b884e0f0e5 --- /dev/null +++ b/src/tools/clippy/tests/ui/uninit.rs @@ -0,0 +1,22 @@ +#![feature(stmt_expr_attributes)] + +use std::mem::MaybeUninit; + +fn main() { + let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + + // edge case: For now we lint on empty arrays + let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; + + // edge case: For now we accept unit tuples + let _: () = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because `MaybeUninit` allows uninitialized data. + let _: MaybeUninit = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because all constitutent types are uninit-compatible. + let _: (MaybeUninit, MaybeUninit) = unsafe { MaybeUninit::uninit().assume_init() }; + + // This is OK, because all constitutent types are uninit-compatible. + let _: (MaybeUninit, [MaybeUninit; 2]) = unsafe { MaybeUninit::uninit().assume_init() }; +} diff --git a/src/tools/clippy/tests/ui/uninit.stderr b/src/tools/clippy/tests/ui/uninit.stderr new file mode 100644 index 0000000000000..a37233ecddaee --- /dev/null +++ b/src/tools/clippy/tests/ui/uninit.stderr @@ -0,0 +1,16 @@ +error: this call for this type may be undefined behavior + --> $DIR/uninit.rs:6:29 + | +LL | let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[deny(clippy::uninit_assumed_init)]` on by default + +error: this call for this type may be undefined behavior + --> $DIR/uninit.rs:9:31 + | +LL | let _: [u8; 0] = unsafe { MaybeUninit::uninit().assume_init() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unit_arg.fixed b/src/tools/clippy/tests/ui/unit_arg.fixed new file mode 100644 index 0000000000000..a739cf7ad814e --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg.fixed @@ -0,0 +1,64 @@ +// run-rustfix +#![warn(clippy::unit_arg)] +#![allow(unused_braces, clippy::no_effect, unused_must_use)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +struct Bar; + +impl Bar { + fn bar(&self, t: T) { + println!("{:?}", t); + } +} + +fn bad() { + foo(()); + foo(()); + foo(()); + foo(()); + foo3((), 2, 2); + let b = Bar; + b.bar(()); +} + +fn ok() { + foo(()); + foo(1); + foo({ 1 }); + foo3("a", 3, vec![3]); + let b = Bar; + b.bar({ 1 }); + b.bar(()); + question_mark(); +} + +fn question_mark() -> Result<(), ()> { + Ok(Ok(())?)?; + Ok(Ok(()))??; + Ok(()) +} + +#[allow(dead_code)] +mod issue_2945 { + fn unit_fn() -> Result<(), i32> { + Ok(()) + } + + fn fallible() -> Result<(), i32> { + Ok(unit_fn()?) + } +} + +fn main() { + bad(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/unit_arg.rs b/src/tools/clippy/tests/ui/unit_arg.rs new file mode 100644 index 0000000000000..d90c49f79de62 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg.rs @@ -0,0 +1,71 @@ +// run-rustfix +#![warn(clippy::unit_arg)] +#![allow(unused_braces, clippy::no_effect, unused_must_use)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +struct Bar; + +impl Bar { + fn bar(&self, t: T) { + println!("{:?}", t); + } +} + +fn bad() { + foo({}); + foo({ + 1; + }); + foo(foo(1)); + foo({ + foo(1); + foo(2); + }); + foo3({}, 2, 2); + let b = Bar; + b.bar({ + 1; + }); +} + +fn ok() { + foo(()); + foo(1); + foo({ 1 }); + foo3("a", 3, vec![3]); + let b = Bar; + b.bar({ 1 }); + b.bar(()); + question_mark(); +} + +fn question_mark() -> Result<(), ()> { + Ok(Ok(())?)?; + Ok(Ok(()))??; + Ok(()) +} + +#[allow(dead_code)] +mod issue_2945 { + fn unit_fn() -> Result<(), i32> { + Ok(()) + } + + fn fallible() -> Result<(), i32> { + Ok(unit_fn()?) + } +} + +fn main() { + bad(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/unit_arg.stderr b/src/tools/clippy/tests/ui/unit_arg.stderr new file mode 100644 index 0000000000000..21ccc684ea9de --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_arg.stderr @@ -0,0 +1,79 @@ +error: passing a unit value to a function + --> $DIR/unit_arg.rs:24:9 + | +LL | foo({}); + | ^^ + | + = note: `-D clippy::unit-arg` implied by `-D warnings` +help: if you intended to pass a unit value, use a unit literal instead + | +LL | foo(()); + | ^^ + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:25:9 + | +LL | foo({ + | _________^ +LL | | 1; +LL | | }); + | |_____^ + | +help: if you intended to pass a unit value, use a unit literal instead + | +LL | foo(()); + | ^^ + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:28:9 + | +LL | foo(foo(1)); + | ^^^^^^ + | +help: if you intended to pass a unit value, use a unit literal instead + | +LL | foo(()); + | ^^ + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:29:9 + | +LL | foo({ + | _________^ +LL | | foo(1); +LL | | foo(2); +LL | | }); + | |_____^ + | +help: if you intended to pass a unit value, use a unit literal instead + | +LL | foo(()); + | ^^ + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:33:10 + | +LL | foo3({}, 2, 2); + | ^^ + | +help: if you intended to pass a unit value, use a unit literal instead + | +LL | foo3((), 2, 2); + | ^^ + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:35:11 + | +LL | b.bar({ + | ___________^ +LL | | 1; +LL | | }); + | |_____^ + | +help: if you intended to pass a unit value, use a unit literal instead + | +LL | b.bar(()); + | ^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unit_cmp.rs b/src/tools/clippy/tests/ui/unit_cmp.rs new file mode 100644 index 0000000000000..8d3a4eed82e3e --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_cmp.rs @@ -0,0 +1,57 @@ +#![warn(clippy::unit_cmp)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +#[derive(PartialEq)] +pub struct ContainsUnit(()); // should be fine + +fn main() { + // this is fine + if true == false {} + + // this warns + if { + true; + } == { + false; + } {} + + if { + true; + } > { + false; + } {} + + assert_eq!( + { + true; + }, + { + false; + } + ); + debug_assert_eq!( + { + true; + }, + { + false; + } + ); + + assert_ne!( + { + true; + }, + { + false; + } + ); + debug_assert_ne!( + { + true; + }, + { + false; + } + ); +} diff --git a/src/tools/clippy/tests/ui/unit_cmp.stderr b/src/tools/clippy/tests/ui/unit_cmp.stderr new file mode 100644 index 0000000000000..c8c0a85dfc102 --- /dev/null +++ b/src/tools/clippy/tests/ui/unit_cmp.stderr @@ -0,0 +1,82 @@ +error: ==-comparison of unit values detected. This will always be true + --> $DIR/unit_cmp.rs:12:8 + | +LL | if { + | ________^ +LL | | true; +LL | | } == { +LL | | false; +LL | | } {} + | |_____^ + | + = note: `-D clippy::unit-cmp` implied by `-D warnings` + +error: >-comparison of unit values detected. This will always be false + --> $DIR/unit_cmp.rs:18:8 + | +LL | if { + | ________^ +LL | | true; +LL | | } > { +LL | | false; +LL | | } {} + | |_____^ + +error: `assert_eq` of unit values detected. This will always succeed + --> $DIR/unit_cmp.rs:24:5 + | +LL | / assert_eq!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `debug_assert_eq` of unit values detected. This will always succeed + --> $DIR/unit_cmp.rs:32:5 + | +LL | / debug_assert_eq!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `assert_ne` of unit values detected. This will always fail + --> $DIR/unit_cmp.rs:41:5 + | +LL | / assert_ne!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `debug_assert_ne` of unit values detected. This will always fail + --> $DIR/unit_cmp.rs:49:5 + | +LL | / debug_assert_ne!( +LL | | { +LL | | true; +LL | | }, +... | +LL | | } +LL | | ); + | |______^ + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unknown_attribute.rs b/src/tools/clippy/tests/ui/unknown_attribute.rs new file mode 100644 index 0000000000000..e993e63f8ed86 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_attribute.rs @@ -0,0 +1,3 @@ +#[clippy::unknown] +#[clippy::cognitive_complexity = "1"] +fn main() {} diff --git a/src/tools/clippy/tests/ui/unknown_attribute.stderr b/src/tools/clippy/tests/ui/unknown_attribute.stderr new file mode 100644 index 0000000000000..47e37aed2464e --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_attribute.stderr @@ -0,0 +1,8 @@ +error: Usage of unknown attribute + --> $DIR/unknown_attribute.rs:1:11 + | +LL | #[clippy::unknown] + | ^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed b/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed new file mode 100644 index 0000000000000..4249ff8a958d1 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::pedantic)] +// Should suggest lowercase +#![allow(clippy::all)] +#![warn(clippy::cmp_nan)] + +// Should suggest similar clippy lint name +#[warn(clippy::if_not_else)] +#[warn(clippy::unnecessary_cast)] +#[warn(clippy::useless_transmute)] +// Shouldn't suggest rustc lint name(`dead_code`) +#[warn(clippy::drop_copy)] +// Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`) +#[warn(clippy::unused_self)] +// Shouldn't suggest renamed clippy lint name(`const_static_lifetime`) +#[warn(clippy::redundant_static_lifetimes)] +fn main() {} diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.rs b/src/tools/clippy/tests/ui/unknown_clippy_lints.rs new file mode 100644 index 0000000000000..5db345f544413 --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#![warn(clippy::pedantic)] +// Should suggest lowercase +#![allow(clippy::All)] +#![warn(clippy::CMP_NAN)] + +// Should suggest similar clippy lint name +#[warn(clippy::if_not_els)] +#[warn(clippy::UNNecsaRy_cAst)] +#[warn(clippy::useles_transute)] +// Shouldn't suggest rustc lint name(`dead_code`) +#[warn(clippy::dead_cod)] +// Shouldn't suggest removed/deprecated clippy lint name(`unused_collect`) +#[warn(clippy::unused_colle)] +// Shouldn't suggest renamed clippy lint name(`const_static_lifetime`) +#[warn(clippy::const_static_lifetim)] +fn main() {} diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr new file mode 100644 index 0000000000000..1b859043bb53b --- /dev/null +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr @@ -0,0 +1,52 @@ +error: unknown clippy lint: clippy::if_not_els + --> $DIR/unknown_clippy_lints.rs:9:8 + | +LL | #[warn(clippy::if_not_els)] + | ^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::if_not_else` + | + = note: `-D clippy::unknown-clippy-lints` implied by `-D warnings` + +error: unknown clippy lint: clippy::UNNecsaRy_cAst + --> $DIR/unknown_clippy_lints.rs:10:8 + | +LL | #[warn(clippy::UNNecsaRy_cAst)] + | ^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::unnecessary_cast` + +error: unknown clippy lint: clippy::useles_transute + --> $DIR/unknown_clippy_lints.rs:11:8 + | +LL | #[warn(clippy::useles_transute)] + | ^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::useless_transmute` + +error: unknown clippy lint: clippy::dead_cod + --> $DIR/unknown_clippy_lints.rs:13:8 + | +LL | #[warn(clippy::dead_cod)] + | ^^^^^^^^^^^^^^^^ help: did you mean: `clippy::drop_copy` + +error: unknown clippy lint: clippy::unused_colle + --> $DIR/unknown_clippy_lints.rs:15:8 + | +LL | #[warn(clippy::unused_colle)] + | ^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::unused_self` + +error: unknown clippy lint: clippy::const_static_lifetim + --> $DIR/unknown_clippy_lints.rs:17:8 + | +LL | #[warn(clippy::const_static_lifetim)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::redundant_static_lifetimes` + +error: unknown clippy lint: clippy::All + --> $DIR/unknown_clippy_lints.rs:5:10 + | +LL | #![allow(clippy::All)] + | ^^^^^^^^^^^ help: lowercase the lint name: `clippy::all` + +error: unknown clippy lint: clippy::CMP_NAN + --> $DIR/unknown_clippy_lints.rs:6:9 + | +LL | #![warn(clippy::CMP_NAN)] + | ^^^^^^^^^^^^^^^ help: lowercase the lint name: `clippy::cmp_nan` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.rs b/src/tools/clippy/tests/ui/unnecessary_cast.rs new file mode 100644 index 0000000000000..df9b227eeb3f5 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast.rs @@ -0,0 +1,23 @@ +#![warn(clippy::unnecessary_cast)] +#![allow(clippy::no_effect)] + +fn main() { + // Test cast_unnecessary + 1i32 as i32; + 1f32 as f32; + false as bool; + &1i32 as &i32; + + // macro version + macro_rules! foo { + ($a:ident, $b:ident) => { + #[allow(unused)] + pub fn $a() -> $b { + 1 as $b + } + }; + } + foo!(a, i32); + foo!(b, f32); + foo!(c, f64); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_cast.stderr b/src/tools/clippy/tests/ui/unnecessary_cast.stderr new file mode 100644 index 0000000000000..8981d13e8eabb --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast.stderr @@ -0,0 +1,22 @@ +error: casting to the same type is unnecessary (`i32` -> `i32`) + --> $DIR/unnecessary_cast.rs:6:5 + | +LL | 1i32 as i32; + | ^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: casting to the same type is unnecessary (`f32` -> `f32`) + --> $DIR/unnecessary_cast.rs:7:5 + | +LL | 1f32 as f32; + | ^^^^^^^^^^^ + +error: casting to the same type is unnecessary (`bool` -> `bool`) + --> $DIR/unnecessary_cast.rs:8:5 + | +LL | false as bool; + | ^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_cast_fixable.fixed b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.fixed new file mode 100644 index 0000000000000..fb89a9fce3d5b --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.fixed @@ -0,0 +1,23 @@ +// run-rustfix + +#![warn(clippy::unnecessary_cast)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + // casting integer literal to float is unnecessary + 100_f32; + 100_f64; + 100_f64; + // Should not trigger + #[rustfmt::skip] + let v = vec!(1); + &v as &[i32]; + 1.0 as f64; + 1 as u64; + 0x10 as f32; + 0o10 as f32; + 0b10 as f32; + 0x11 as f64; + 0o11 as f64; + 0b11 as f64; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_cast_fixable.rs b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.rs new file mode 100644 index 0000000000000..4a0c8620dc134 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.rs @@ -0,0 +1,23 @@ +// run-rustfix + +#![warn(clippy::unnecessary_cast)] +#![allow(clippy::no_effect, clippy::unnecessary_operation)] + +fn main() { + // casting integer literal to float is unnecessary + 100 as f32; + 100 as f64; + 100_i32 as f64; + // Should not trigger + #[rustfmt::skip] + let v = vec!(1); + &v as &[i32]; + 1.0 as f64; + 1 as u64; + 0x10 as f32; + 0o10 as f32; + 0b10 as f32; + 0x11 as f64; + 0o11 as f64; + 0b11 as f64; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_cast_fixable.stderr b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.stderr new file mode 100644 index 0000000000000..8ff1e5dea6003 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_cast_fixable.stderr @@ -0,0 +1,22 @@ +error: casting integer literal to `f32` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:8:5 + | +LL | 100 as f32; + | ^^^^^^^^^^ help: try: `100_f32` + | + = note: `-D clippy::unnecessary-cast` implied by `-D warnings` + +error: casting integer literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:9:5 + | +LL | 100 as f64; + | ^^^^^^^^^^ help: try: `100_f64` + +error: casting integer literal to `f64` is unnecessary + --> $DIR/unnecessary_cast_fixable.rs:10:5 + | +LL | 100_i32 as f64; + | ^^^^^^^^^^^^^^ help: try: `100_f64` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_clone.rs b/src/tools/clippy/tests/ui/unnecessary_clone.rs new file mode 100644 index 0000000000000..f1cc5b564c1dc --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_clone.rs @@ -0,0 +1,117 @@ +// does not test any rustfixable lints + +#![warn(clippy::clone_on_ref_ptr)] +#![allow(unused, clippy::redundant_clone)] + +use std::cell::RefCell; +use std::rc::{self, Rc}; +use std::sync::{self, Arc}; + +trait SomeTrait {} +struct SomeImpl; +impl SomeTrait for SomeImpl {} + +fn main() {} + +fn is_ascii(ch: char) -> bool { + ch.is_ascii() +} + +fn clone_on_copy() { + 42.clone(); + + vec![1].clone(); // ok, not a Copy type + Some(vec![1]).clone(); // ok, not a Copy type + (&42).clone(); + + let rc = RefCell::new(0); + rc.borrow().clone(); + + // Issue #4348 + let mut x = 43; + let _ = &x.clone(); // ok, getting a ref + 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate + is_ascii('z'.clone()); + + // Issue #5436 + let mut vec = Vec::new(); + vec.push(42.clone()); +} + +fn clone_on_ref_ptr() { + let rc = Rc::new(true); + let arc = Arc::new(true); + + let rcweak = Rc::downgrade(&rc); + let arc_weak = Arc::downgrade(&arc); + + rc.clone(); + Rc::clone(&rc); + + arc.clone(); + Arc::clone(&arc); + + rcweak.clone(); + rc::Weak::clone(&rcweak); + + arc_weak.clone(); + sync::Weak::clone(&arc_weak); + + let x = Arc::new(SomeImpl); + let _: Arc = x.clone(); +} + +fn clone_on_copy_generic(t: T) { + t.clone(); + + Some(t).clone(); +} + +fn clone_on_double_ref() { + let x = vec![1]; + let y = &&x; + let z: &Vec<_> = y.clone(); + + println!("{:p} {:p}", *y, z); +} + +mod many_derefs { + struct A; + struct B; + struct C; + struct D; + #[derive(Copy, Clone)] + struct E; + + macro_rules! impl_deref { + ($src:ident, $dst:ident) => { + impl std::ops::Deref for $src { + type Target = $dst; + fn deref(&self) -> &Self::Target { + &$dst + } + } + }; + } + + impl_deref!(A, B); + impl_deref!(B, C); + impl_deref!(C, D); + impl std::ops::Deref for D { + type Target = &'static E; + fn deref(&self) -> &Self::Target { + &&E + } + } + + fn go1() { + let a = A; + let _: E = a.clone(); + let _: E = *****a; + } + + fn check(mut encoded: &[u8]) { + let _ = &mut encoded.clone(); + let _ = &encoded.clone(); + } +} diff --git a/src/tools/clippy/tests/ui/unnecessary_clone.stderr b/src/tools/clippy/tests/ui/unnecessary_clone.stderr new file mode 100644 index 0000000000000..6176a2bc46479 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_clone.stderr @@ -0,0 +1,130 @@ +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:21:5 + | +LL | 42.clone(); + | ^^^^^^^^^^ help: try removing the `clone` call: `42` + | + = note: `-D clippy::clone-on-copy` implied by `-D warnings` + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:25:5 + | +LL | (&42).clone(); + | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)` + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:28:5 + | +LL | rc.borrow().clone(); + | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()` + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:34:14 + | +LL | is_ascii('z'.clone()); + | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'` + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:38:14 + | +LL | vec.push(42.clone()); + | ^^^^^^^^^^ help: try removing the `clone` call: `42` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:48:5 + | +LL | rc.clone(); + | ^^^^^^^^^^ help: try this: `Rc::::clone(&rc)` + | + = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:51:5 + | +LL | arc.clone(); + | ^^^^^^^^^^^ help: try this: `Arc::::clone(&arc)` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:54:5 + | +LL | rcweak.clone(); + | ^^^^^^^^^^^^^^ help: try this: `Weak::::clone(&rcweak)` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:57:5 + | +LL | arc_weak.clone(); + | ^^^^^^^^^^^^^^^^ help: try this: `Weak::::clone(&arc_weak)` + +error: using `.clone()` on a ref-counted pointer + --> $DIR/unnecessary_clone.rs:61:33 + | +LL | let _: Arc = x.clone(); + | ^^^^^^^^^ help: try this: `Arc::::clone(&x)` + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:65:5 + | +LL | t.clone(); + | ^^^^^^^^^ help: try removing the `clone` call: `t` + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:67:5 + | +LL | Some(t).clone(); + | ^^^^^^^^^^^^^^^ help: try removing the `clone` call: `Some(t)` + +error: using `clone` on a double-reference; this will copy the reference instead of cloning the inner type + --> $DIR/unnecessary_clone.rs:73:22 + | +LL | let z: &Vec<_> = y.clone(); + | ^^^^^^^^^ + | + = note: `#[deny(clippy::clone_double_ref)]` on by default +help: try dereferencing it + | +LL | let z: &Vec<_> = &(*y).clone(); + | ^^^^^^^^^^^^^ +help: or try being explicit if you are sure, that you want to clone a reference + | +LL | let z: &Vec<_> = <&std::vec::Vec>::clone(y); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `clone` on a `Copy` type + --> $DIR/unnecessary_clone.rs:109:20 + | +LL | let _: E = a.clone(); + | ^^^^^^^^^ help: try dereferencing it: `*****a` + +error: using `clone` on a double-reference; this will copy the reference instead of cloning the inner type + --> $DIR/unnecessary_clone.rs:114:22 + | +LL | let _ = &mut encoded.clone(); + | ^^^^^^^^^^^^^^^ + | +help: try dereferencing it + | +LL | let _ = &mut &(*encoded).clone(); + | ^^^^^^^^^^^^^^^^^^^ +help: or try being explicit if you are sure, that you want to clone a reference + | +LL | let _ = &mut <&[u8]>::clone(encoded); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: using `clone` on a double-reference; this will copy the reference instead of cloning the inner type + --> $DIR/unnecessary_clone.rs:115:18 + | +LL | let _ = &encoded.clone(); + | ^^^^^^^^^^^^^^^ + | +help: try dereferencing it + | +LL | let _ = &&(*encoded).clone(); + | ^^^^^^^^^^^^^^^^^^^ +help: or try being explicit if you are sure, that you want to clone a reference + | +LL | let _ = &<&[u8]>::clone(encoded); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 16 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.rs b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs new file mode 100644 index 0000000000000..af858e4abcf55 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.rs @@ -0,0 +1,17 @@ +fn main() { + let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); + let _ = (0..4).filter_map(|x| { + if x > 1 { + return Some(x); + }; + None + }); + let _ = (0..4).filter_map(|x| match x { + 0 | 1 => None, + _ => Some(x), + }); + + let _ = (0..4).filter_map(|x| Some(x + 1)); + + let _ = (0..4).filter_map(i32::checked_abs); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr new file mode 100644 index 0000000000000..041829c3c7855 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_filter_map.stderr @@ -0,0 +1,38 @@ +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:2:13 + | +LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings` + +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:3:13 + | +LL | let _ = (0..4).filter_map(|x| { + | _____________^ +LL | | if x > 1 { +LL | | return Some(x); +LL | | }; +LL | | None +LL | | }); + | |______^ + +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:9:13 + | +LL | let _ = (0..4).filter_map(|x| match x { + | _____________^ +LL | | 0 | 1 => None, +LL | | _ => Some(x), +LL | | }); + | |______^ + +error: this `.filter_map` can be written more simply using `.map` + --> $DIR/unnecessary_filter_map.rs:14:13 + | +LL | let _ = (0..4).filter_map(|x| Some(x + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_flat_map.fixed b/src/tools/clippy/tests/ui/unnecessary_flat_map.fixed new file mode 100644 index 0000000000000..dfe3bd47e1394 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_flat_map.fixed @@ -0,0 +1,14 @@ +// run-rustfix + +#![allow(unused_imports)] +#![warn(clippy::flat_map_identity)] + +use std::convert; + +fn main() { + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flatten(); + + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flatten(); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_flat_map.rs b/src/tools/clippy/tests/ui/unnecessary_flat_map.rs new file mode 100644 index 0000000000000..393b95692554c --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_flat_map.rs @@ -0,0 +1,14 @@ +// run-rustfix + +#![allow(unused_imports)] +#![warn(clippy::flat_map_identity)] + +use std::convert; + +fn main() { + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flat_map(|x| x); + + let iterator = [[0, 1], [2, 3], [4, 5]].iter(); + let _ = iterator.flat_map(convert::identity); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_flat_map.stderr b/src/tools/clippy/tests/ui/unnecessary_flat_map.stderr new file mode 100644 index 0000000000000..a1cd5745e4949 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_flat_map.stderr @@ -0,0 +1,16 @@ +error: called `flat_map(|x| x)` on an `Iterator` + --> $DIR/unnecessary_flat_map.rs:10:22 + | +LL | let _ = iterator.flat_map(|x| x); + | ^^^^^^^^^^^^^^^ help: try: `flatten()` + | + = note: `-D clippy::flat-map-identity` implied by `-D warnings` + +error: called `flat_map(std::convert::identity)` on an `Iterator` + --> $DIR/unnecessary_flat_map.rs:13:22 + | +LL | let _ = iterator.flat_map(convert::identity); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_fold.fixed b/src/tools/clippy/tests/ui/unnecessary_fold.fixed new file mode 100644 index 0000000000000..52300a3b64061 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_fold.fixed @@ -0,0 +1,52 @@ +// run-rustfix + +#![allow(dead_code)] + +/// Calls which should trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold() { + // Can be replaced by .any + let _ = (0..3).any(|x| x > 2); + // Can be replaced by .all + let _ = (0..3).all(|x| x > 2); + // Can be replaced by .sum + let _: i32 = (0..3).sum(); + // Can be replaced by .product + let _: i32 = (0..3).product(); +} + +/// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)` +fn unnecessary_fold_span_for_multi_element_chain() { + let _: bool = (0..3).map(|x| 2 * x).any(|x| x > 2); +} + +/// Calls which should not trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold_should_ignore() { + let _ = (0..3).fold(true, |acc, x| acc || x > 2); + let _ = (0..3).fold(false, |acc, x| acc && x > 2); + let _ = (0..3).fold(1, |acc, x| acc + x); + let _ = (0..3).fold(0, |acc, x| acc * x); + let _ = (0..3).fold(0, |acc, x| 1 + acc + x); + + // We only match against an accumulator on the left + // hand side. We could lint for .sum and .product when + // it's on the right, but don't for now (and this wouldn't + // be valid if we extended the lint to cover arbitrary numeric + // types). + let _ = (0..3).fold(false, |acc, x| x > 2 || acc); + let _ = (0..3).fold(true, |acc, x| x > 2 && acc); + let _ = (0..3).fold(0, |acc, x| x + acc); + let _ = (0..3).fold(1, |acc, x| x * acc); + + let _ = [(0..2), (0..3)].iter().fold(0, |a, b| a + b.len()); + let _ = [(0..2), (0..3)].iter().fold(1, |a, b| a * b.len()); +} + +/// Should lint only the line containing the fold +fn unnecessary_fold_over_multiple_lines() { + let _ = (0..3) + .map(|x| x + 1) + .filter(|x| x % 2 == 0) + .any(|x| x > 2); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unnecessary_fold.rs b/src/tools/clippy/tests/ui/unnecessary_fold.rs new file mode 100644 index 0000000000000..4028d80c0a3cb --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_fold.rs @@ -0,0 +1,52 @@ +// run-rustfix + +#![allow(dead_code)] + +/// Calls which should trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold() { + // Can be replaced by .any + let _ = (0..3).fold(false, |acc, x| acc || x > 2); + // Can be replaced by .all + let _ = (0..3).fold(true, |acc, x| acc && x > 2); + // Can be replaced by .sum + let _: i32 = (0..3).fold(0, |acc, x| acc + x); + // Can be replaced by .product + let _: i32 = (0..3).fold(1, |acc, x| acc * x); +} + +/// Should trigger the `UNNECESSARY_FOLD` lint, with an error span including exactly `.fold(...)` +fn unnecessary_fold_span_for_multi_element_chain() { + let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2); +} + +/// Calls which should not trigger the `UNNECESSARY_FOLD` lint +fn unnecessary_fold_should_ignore() { + let _ = (0..3).fold(true, |acc, x| acc || x > 2); + let _ = (0..3).fold(false, |acc, x| acc && x > 2); + let _ = (0..3).fold(1, |acc, x| acc + x); + let _ = (0..3).fold(0, |acc, x| acc * x); + let _ = (0..3).fold(0, |acc, x| 1 + acc + x); + + // We only match against an accumulator on the left + // hand side. We could lint for .sum and .product when + // it's on the right, but don't for now (and this wouldn't + // be valid if we extended the lint to cover arbitrary numeric + // types). + let _ = (0..3).fold(false, |acc, x| x > 2 || acc); + let _ = (0..3).fold(true, |acc, x| x > 2 && acc); + let _ = (0..3).fold(0, |acc, x| x + acc); + let _ = (0..3).fold(1, |acc, x| x * acc); + + let _ = [(0..2), (0..3)].iter().fold(0, |a, b| a + b.len()); + let _ = [(0..2), (0..3)].iter().fold(1, |a, b| a * b.len()); +} + +/// Should lint only the line containing the fold +fn unnecessary_fold_over_multiple_lines() { + let _ = (0..3) + .map(|x| x + 1) + .filter(|x| x % 2 == 0) + .fold(false, |acc, x| acc || x > 2); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unnecessary_fold.stderr b/src/tools/clippy/tests/ui/unnecessary_fold.stderr new file mode 100644 index 0000000000000..22c44588ab7af --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_fold.stderr @@ -0,0 +1,40 @@ +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:8:20 + | +LL | let _ = (0..3).fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + | + = note: `-D clippy::unnecessary-fold` implied by `-D warnings` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:10:20 + | +LL | let _ = (0..3).fold(true, |acc, x| acc && x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `all(|x| x > 2)` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:12:25 + | +LL | let _: i32 = (0..3).fold(0, |acc, x| acc + x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `sum()` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:14:25 + | +LL | let _: i32 = (0..3).fold(1, |acc, x| acc * x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `product()` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:19:41 + | +LL | let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + +error: this `.fold` can be written more succinctly using another method + --> $DIR/unnecessary_fold.rs:49:10 + | +LL | .fold(false, |acc, x| acc || x > 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.fixed b/src/tools/clippy/tests/ui/unnecessary_operation.fixed new file mode 100644 index 0000000000000..2fca96c4cd556 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_operation.fixed @@ -0,0 +1,79 @@ +// run-rustfix + +#![feature(box_syntax)] +#![allow(clippy::deref_addrof, dead_code, unused, clippy::no_effect)] +#![warn(clippy::unnecessary_operation)] + +struct Tuple(i32); +struct Struct { + field: i32, +} +enum Enum { + Tuple(i32), + Struct { field: i32 }, +} +struct DropStruct { + field: i32, +} +impl Drop for DropStruct { + fn drop(&mut self) {} +} +struct DropTuple(i32); +impl Drop for DropTuple { + fn drop(&mut self) {} +} +enum DropEnum { + Tuple(i32), + Struct { field: i32 }, +} +impl Drop for DropEnum { + fn drop(&mut self) {} +} +struct FooString { + s: String, +} + +fn get_number() -> i32 { + 0 +} + +fn get_usize() -> usize { + 0 +} +fn get_struct() -> Struct { + Struct { field: 0 } +} +fn get_drop_struct() -> DropStruct { + DropStruct { field: 0 } +} + +fn main() { + get_number(); + get_number(); + get_struct(); + get_number(); + get_number(); + 5;get_number(); + get_number(); + get_number(); + 5;6;get_number(); + get_number(); + get_number(); + get_number(); + 5;get_number(); + 42;get_number(); + [42, 55];get_usize(); + 42;get_number(); + get_number(); + [42; 55];get_usize(); + get_number(); + String::from("blah"); + + // Do not warn + DropTuple(get_number()); + DropStruct { field: get_number() }; + DropStruct { field: get_number() }; + DropStruct { ..get_drop_struct() }; + DropEnum::Tuple(get_number()); + DropEnum::Struct { field: get_number() }; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.rs b/src/tools/clippy/tests/ui/unnecessary_operation.rs new file mode 100644 index 0000000000000..08cb9ab522ee0 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_operation.rs @@ -0,0 +1,83 @@ +// run-rustfix + +#![feature(box_syntax)] +#![allow(clippy::deref_addrof, dead_code, unused, clippy::no_effect)] +#![warn(clippy::unnecessary_operation)] + +struct Tuple(i32); +struct Struct { + field: i32, +} +enum Enum { + Tuple(i32), + Struct { field: i32 }, +} +struct DropStruct { + field: i32, +} +impl Drop for DropStruct { + fn drop(&mut self) {} +} +struct DropTuple(i32); +impl Drop for DropTuple { + fn drop(&mut self) {} +} +enum DropEnum { + Tuple(i32), + Struct { field: i32 }, +} +impl Drop for DropEnum { + fn drop(&mut self) {} +} +struct FooString { + s: String, +} + +fn get_number() -> i32 { + 0 +} + +fn get_usize() -> usize { + 0 +} +fn get_struct() -> Struct { + Struct { field: 0 } +} +fn get_drop_struct() -> DropStruct { + DropStruct { field: 0 } +} + +fn main() { + Tuple(get_number()); + Struct { field: get_number() }; + Struct { ..get_struct() }; + Enum::Tuple(get_number()); + Enum::Struct { field: get_number() }; + 5 + get_number(); + *&get_number(); + &get_number(); + (5, 6, get_number()); + box get_number(); + get_number()..; + ..get_number(); + 5..get_number(); + [42, get_number()]; + [42, 55][get_usize()]; + (42, get_number()).1; + [get_number(); 55]; + [42; 55][get_usize()]; + { + get_number() + }; + FooString { + s: String::from("blah"), + }; + + // Do not warn + DropTuple(get_number()); + DropStruct { field: get_number() }; + DropStruct { field: get_number() }; + DropStruct { ..get_drop_struct() }; + DropEnum::Tuple(get_number()); + DropEnum::Struct { field: get_number() }; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_operation.stderr b/src/tools/clippy/tests/ui/unnecessary_operation.stderr new file mode 100644 index 0000000000000..f88c9f9908bea --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_operation.stderr @@ -0,0 +1,128 @@ +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:51:5 + | +LL | Tuple(get_number()); + | ^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + | + = note: `-D clippy::unnecessary-operation` implied by `-D warnings` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:52:5 + | +LL | Struct { field: get_number() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:53:5 + | +LL | Struct { ..get_struct() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_struct();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:54:5 + | +LL | Enum::Tuple(get_number()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:55:5 + | +LL | Enum::Struct { field: get_number() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:56:5 + | +LL | 5 + get_number(); + | ^^^^^^^^^^^^^^^^^ help: replace it with: `5;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:57:5 + | +LL | *&get_number(); + | ^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:58:5 + | +LL | &get_number(); + | ^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:59:5 + | +LL | (5, 6, get_number()); + | ^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `5;6;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:60:5 + | +LL | box get_number(); + | ^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:61:5 + | +LL | get_number()..; + | ^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:62:5 + | +LL | ..get_number(); + | ^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:63:5 + | +LL | 5..get_number(); + | ^^^^^^^^^^^^^^^^ help: replace it with: `5;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:64:5 + | +LL | [42, get_number()]; + | ^^^^^^^^^^^^^^^^^^^ help: replace it with: `42;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:65:5 + | +LL | [42, 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `[42, 55];get_usize();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:66:5 + | +LL | (42, get_number()).1; + | ^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `42;get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:67:5 + | +LL | [get_number(); 55]; + | ^^^^^^^^^^^^^^^^^^^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:68:5 + | +LL | [42; 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `[42; 55];get_usize();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:69:5 + | +LL | / { +LL | | get_number() +LL | | }; + | |______^ help: replace it with: `get_number();` + +error: statement can be reduced + --> $DIR/unnecessary_operation.rs:72:5 + | +LL | / FooString { +LL | | s: String::from("blah"), +LL | | }; + | |______^ help: replace it with: `String::from("blah");` + +error: aborting due to 20 previous errors + diff --git a/src/tools/clippy/tests/ui/unnecessary_ref.fixed b/src/tools/clippy/tests/ui/unnecessary_ref.fixed new file mode 100644 index 0000000000000..f7b94118d4e86 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_ref.fixed @@ -0,0 +1,14 @@ +// run-rustfix + +#![feature(stmt_expr_attributes)] +#![allow(unused_variables)] + +struct Outer { + inner: u32, +} + +#[deny(clippy::ref_in_deref)] +fn main() { + let outer = Outer { inner: 0 }; + let inner = outer.inner; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_ref.rs b/src/tools/clippy/tests/ui/unnecessary_ref.rs new file mode 100644 index 0000000000000..4e585b9b96ba9 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_ref.rs @@ -0,0 +1,14 @@ +// run-rustfix + +#![feature(stmt_expr_attributes)] +#![allow(unused_variables)] + +struct Outer { + inner: u32, +} + +#[deny(clippy::ref_in_deref)] +fn main() { + let outer = Outer { inner: 0 }; + let inner = (&outer).inner; +} diff --git a/src/tools/clippy/tests/ui/unnecessary_ref.stderr b/src/tools/clippy/tests/ui/unnecessary_ref.stderr new file mode 100644 index 0000000000000..34ba167a94790 --- /dev/null +++ b/src/tools/clippy/tests/ui/unnecessary_ref.stderr @@ -0,0 +1,14 @@ +error: Creating a reference that is immediately dereferenced. + --> $DIR/unnecessary_ref.rs:13:17 + | +LL | let inner = (&outer).inner; + | ^^^^^^^^ help: try this: `outer` + | +note: the lint level is defined here + --> $DIR/unnecessary_ref.rs:10:8 + | +LL | #[deny(clippy::ref_in_deref)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/unneeded_field_pattern.rs b/src/tools/clippy/tests/ui/unneeded_field_pattern.rs new file mode 100644 index 0000000000000..fa639aa70d61d --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_field_pattern.rs @@ -0,0 +1,22 @@ +#![warn(clippy::unneeded_field_pattern)] +#[allow(dead_code, unused)] + +struct Foo { + a: i32, + b: i32, + c: i32, +} + +fn main() { + let f = Foo { a: 0, b: 0, c: 0 }; + + match f { + Foo { a: _, b: 0, .. } => {}, + + Foo { a: _, b: _, c: _ } => {}, + } + match f { + Foo { b: 0, .. } => {}, // should be OK + Foo { .. } => {}, // and the Force might be with this one + } +} diff --git a/src/tools/clippy/tests/ui/unneeded_field_pattern.stderr b/src/tools/clippy/tests/ui/unneeded_field_pattern.stderr new file mode 100644 index 0000000000000..e7b92ce1e197b --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_field_pattern.stderr @@ -0,0 +1,19 @@ +error: You matched a field with a wildcard pattern. Consider using `..` instead + --> $DIR/unneeded_field_pattern.rs:14:15 + | +LL | Foo { a: _, b: 0, .. } => {}, + | ^^^^ + | + = note: `-D clippy::unneeded-field-pattern` implied by `-D warnings` + = help: Try with `Foo { b: 0, .. }` + +error: All the struct fields are matched to a wildcard pattern, consider using `..`. + --> $DIR/unneeded_field_pattern.rs:16:9 + | +LL | Foo { a: _, b: _, c: _ } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: Try with `Foo { .. }` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.fixed b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.fixed new file mode 100644 index 0000000000000..12c3461c95579 --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.fixed @@ -0,0 +1,45 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] +#![deny(clippy::unneeded_wildcard_pattern)] + +fn main() { + let t = (0, 1, 2, 3); + + if let (0, ..) = t {}; + if let (0, ..) = t {}; + if let (.., 0) = t {}; + if let (.., 0) = t {}; + if let (0, ..) = t {}; + if let (0, ..) = t {}; + if let (_, 0, ..) = t {}; + if let (.., 0, _) = t {}; + if let (0, _, _, _) = t {}; + if let (0, ..) = t {}; + if let (.., 0) = t {}; + + #[rustfmt::skip] + { + if let (0, ..,) = t {}; + } + + struct S(usize, usize, usize, usize); + + let s = S(0, 1, 2, 3); + + if let S(0, ..) = s {}; + if let S(0, ..) = s {}; + if let S(.., 0) = s {}; + if let S(.., 0) = s {}; + if let S(0, ..) = s {}; + if let S(0, ..) = s {}; + if let S(_, 0, ..) = s {}; + if let S(.., 0, _) = s {}; + if let S(0, _, _, _) = s {}; + if let S(0, ..) = s {}; + if let S(.., 0) = s {}; + + #[rustfmt::skip] + { + if let S(0, ..,) = s {}; + } +} diff --git a/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.rs b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.rs new file mode 100644 index 0000000000000..4ac01d5d23b04 --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.rs @@ -0,0 +1,45 @@ +// run-rustfix +#![feature(stmt_expr_attributes)] +#![deny(clippy::unneeded_wildcard_pattern)] + +fn main() { + let t = (0, 1, 2, 3); + + if let (0, .., _) = t {}; + if let (0, _, ..) = t {}; + if let (_, .., 0) = t {}; + if let (.., _, 0) = t {}; + if let (0, _, _, ..) = t {}; + if let (0, .., _, _) = t {}; + if let (_, 0, ..) = t {}; + if let (.., 0, _) = t {}; + if let (0, _, _, _) = t {}; + if let (0, ..) = t {}; + if let (.., 0) = t {}; + + #[rustfmt::skip] + { + if let (0, .., _, _,) = t {}; + } + + struct S(usize, usize, usize, usize); + + let s = S(0, 1, 2, 3); + + if let S(0, .., _) = s {}; + if let S(0, _, ..) = s {}; + if let S(_, .., 0) = s {}; + if let S(.., _, 0) = s {}; + if let S(0, _, _, ..) = s {}; + if let S(0, .., _, _) = s {}; + if let S(_, 0, ..) = s {}; + if let S(.., 0, _) = s {}; + if let S(0, _, _, _) = s {}; + if let S(0, ..) = s {}; + if let S(.., 0) = s {}; + + #[rustfmt::skip] + { + if let S(0, .., _, _,) = s {}; + } +} diff --git a/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.stderr b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.stderr new file mode 100644 index 0000000000000..716d9ecff89af --- /dev/null +++ b/src/tools/clippy/tests/ui/unneeded_wildcard_pattern.stderr @@ -0,0 +1,92 @@ +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:8:18 + | +LL | if let (0, .., _) = t {}; + | ^^^ help: remove it + | +note: the lint level is defined here + --> $DIR/unneeded_wildcard_pattern.rs:3:9 + | +LL | #![deny(clippy::unneeded_wildcard_pattern)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:9:16 + | +LL | if let (0, _, ..) = t {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:10:13 + | +LL | if let (_, .., 0) = t {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:11:15 + | +LL | if let (.., _, 0) = t {}; + | ^^^ help: remove it + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:12:16 + | +LL | if let (0, _, _, ..) = t {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:13:18 + | +LL | if let (0, .., _, _) = t {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:22:22 + | +LL | if let (0, .., _, _,) = t {}; + | ^^^^^^ help: remove them + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:29:19 + | +LL | if let S(0, .., _) = s {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:30:17 + | +LL | if let S(0, _, ..) = s {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:31:14 + | +LL | if let S(_, .., 0) = s {}; + | ^^^ help: remove it + +error: this pattern is unneeded as the `..` pattern can match that element + --> $DIR/unneeded_wildcard_pattern.rs:32:16 + | +LL | if let S(.., _, 0) = s {}; + | ^^^ help: remove it + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:33:17 + | +LL | if let S(0, _, _, ..) = s {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:34:19 + | +LL | if let S(0, .., _, _) = s {}; + | ^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> $DIR/unneeded_wildcard_pattern.rs:43:23 + | +LL | if let S(0, .., _, _,) = s {}; + | ^^^^^^ help: remove them + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/unreadable_literal.fixed b/src/tools/clippy/tests/ui/unreadable_literal.fixed new file mode 100644 index 0000000000000..3f358d9ecaa0a --- /dev/null +++ b/src/tools/clippy/tests/ui/unreadable_literal.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::unreadable_literal)] + +struct Foo(u64); + +macro_rules! foo { + () => { + Foo(123123123123) + }; +} + +fn main() { + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x1_234_567, + 65536, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = (0b11_0110_i64, 0xcafe_babe_usize, 123_456_f32, 1.234_567_f32); + let _good_sci = 1.1234e1; + let _bad_sci = 1.123_456e1; + + let _fail9 = 0x00ab_cdef; + let _fail10: u32 = 0xBAFE_BAFE; + let _fail11 = 0x0abc_deff; + let _fail12: i128 = 0x00ab_cabc_abca_bcab_cabc; + + let _ = foo!(); +} diff --git a/src/tools/clippy/tests/ui/unreadable_literal.rs b/src/tools/clippy/tests/ui/unreadable_literal.rs new file mode 100644 index 0000000000000..e658a5f28c90e --- /dev/null +++ b/src/tools/clippy/tests/ui/unreadable_literal.rs @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::unreadable_literal)] + +struct Foo(u64); + +macro_rules! foo { + () => { + Foo(123123123123) + }; +} + +fn main() { + let _good = ( + 0b1011_i64, + 0o1_234_u32, + 0x1_234_567, + 65536, + 1_2345_6789, + 1234_f32, + 1_234.12_f32, + 1_234.123_f32, + 1.123_4_f32, + ); + let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + let _good_sci = 1.1234e1; + let _bad_sci = 1.123456e1; + + let _fail9 = 0xabcdef; + let _fail10: u32 = 0xBAFEBAFE; + let _fail11 = 0xabcdeff; + let _fail12: i128 = 0xabcabcabcabcabcabc; + + let _ = foo!(); +} diff --git a/src/tools/clippy/tests/ui/unreadable_literal.stderr b/src/tools/clippy/tests/ui/unreadable_literal.stderr new file mode 100644 index 0000000000000..1b2ff6bff048c --- /dev/null +++ b/src/tools/clippy/tests/ui/unreadable_literal.stderr @@ -0,0 +1,58 @@ +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:25:17 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^^^ help: consider: `0b11_0110_i64` + | + = note: `-D clippy::unreadable-literal` implied by `-D warnings` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:25:31 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^^^^^^^ help: consider: `0xcafe_babe_usize` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:25:49 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^ help: consider: `123_456_f32` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:25:61 + | +LL | let _bad = (0b110110_i64, 0xcafebabe_usize, 123456_f32, 1.234567_f32); + | ^^^^^^^^^^^^ help: consider: `1.234_567_f32` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:27:20 + | +LL | let _bad_sci = 1.123456e1; + | ^^^^^^^^^^ help: consider: `1.123_456e1` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:29:18 + | +LL | let _fail9 = 0xabcdef; + | ^^^^^^^^ help: consider: `0x00ab_cdef` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:30:24 + | +LL | let _fail10: u32 = 0xBAFEBAFE; + | ^^^^^^^^^^ help: consider: `0xBAFE_BAFE` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:31:19 + | +LL | let _fail11 = 0xabcdeff; + | ^^^^^^^^^ help: consider: `0x0abc_deff` + +error: long literal lacking separators + --> $DIR/unreadable_literal.rs:32:25 + | +LL | let _fail12: i128 = 0xabcabcabcabcabcabc; + | ^^^^^^^^^^^^^^^^^^^^ help: consider: `0x00ab_cabc_abca_bcab_cabc` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs new file mode 100644 index 0000000000000..7bee9c499e1f3 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.rs @@ -0,0 +1,60 @@ +#![warn(clippy::unsafe_derive_deserialize)] +#![allow(unused, clippy::missing_safety_doc)] + +extern crate serde; + +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct A {} +impl A { + pub unsafe fn new(_a: i32, _b: i32) -> Self { + Self {} + } +} + +#[derive(Deserialize)] +pub struct B {} +impl B { + pub unsafe fn unsafe_method(&self) {} +} + +#[derive(Deserialize)] +pub struct C {} +impl C { + pub fn unsafe_block(&self) { + unsafe {} + } +} + +#[derive(Deserialize)] +pub struct D {} +impl D { + pub fn inner_unsafe_fn(&self) { + unsafe fn inner() {} + } +} + +// Does not derive `Deserialize`, should be ignored +pub struct E {} +impl E { + pub unsafe fn new(_a: i32, _b: i32) -> Self { + Self {} + } + + pub unsafe fn unsafe_method(&self) {} + + pub fn unsafe_block(&self) { + unsafe {} + } + + pub fn inner_unsafe_fn(&self) { + unsafe fn inner() {} + } +} + +// Does not have methods using `unsafe`, should be ignored +#[derive(Deserialize)] +pub struct F {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr new file mode 100644 index 0000000000000..1978bd95a6703 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_derive_deserialize.stderr @@ -0,0 +1,39 @@ +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:8:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = note: `-D clippy::unsafe-derive-deserialize` implied by `-D warnings` + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:16:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:22:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> $DIR/unsafe_derive_deserialize.rs:30:10 + | +LL | #[derive(Deserialize)] + | ^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/unsafe_removed_from_name.rs b/src/tools/clippy/tests/ui/unsafe_removed_from_name.rs new file mode 100644 index 0000000000000..a1f616733bd92 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_removed_from_name.rs @@ -0,0 +1,27 @@ +#![allow(unused_imports)] +#![allow(dead_code)] +#![warn(clippy::unsafe_removed_from_name)] + +use std::cell::UnsafeCell as TotallySafeCell; + +use std::cell::UnsafeCell as TotallySafeCellAgain; + +// Shouldn't error +use std::cell::RefCell as ProbablyNotUnsafe; +use std::cell::RefCell as RefCellThatCantBeUnsafe; +use std::cell::UnsafeCell as SuperDangerousUnsafeCell; +use std::cell::UnsafeCell as Dangerunsafe; +use std::cell::UnsafeCell as Bombsawayunsafe; + +mod mod_with_some_unsafe_things { + pub struct Safe {} + pub struct Unsafe {} +} + +use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety; + +// Shouldn't error +use mod_with_some_unsafe_things::Safe as IPromiseItsSafeThisTime; +use mod_with_some_unsafe_things::Unsafe as SuperUnsafeModThing; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unsafe_removed_from_name.stderr b/src/tools/clippy/tests/ui/unsafe_removed_from_name.stderr new file mode 100644 index 0000000000000..4f871cbe41b06 --- /dev/null +++ b/src/tools/clippy/tests/ui/unsafe_removed_from_name.stderr @@ -0,0 +1,22 @@ +error: removed `unsafe` from the name of `UnsafeCell` in use as `TotallySafeCell` + --> $DIR/unsafe_removed_from_name.rs:5:1 + | +LL | use std::cell::UnsafeCell as TotallySafeCell; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unsafe-removed-from-name` implied by `-D warnings` + +error: removed `unsafe` from the name of `UnsafeCell` in use as `TotallySafeCellAgain` + --> $DIR/unsafe_removed_from_name.rs:7:1 + | +LL | use std::cell::UnsafeCell as TotallySafeCellAgain; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: removed `unsafe` from the name of `Unsafe` in use as `LieAboutModSafety` + --> $DIR/unsafe_removed_from_name.rs:21:1 + | +LL | use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/unseparated_prefix_literals.fixed b/src/tools/clippy/tests/ui/unseparated_prefix_literals.fixed new file mode 100644 index 0000000000000..3c422cc4fee72 --- /dev/null +++ b/src/tools/clippy/tests/ui/unseparated_prefix_literals.fixed @@ -0,0 +1,41 @@ +// run-rustfix + +#![warn(clippy::unseparated_literal_suffix)] +#![allow(dead_code)] + +#[macro_use] +extern crate clippy_mini_macro_test; + +// Test for proc-macro attribute +#[derive(ClippyMiniMacroTest)] +struct Foo; + +macro_rules! lit_from_macro { + () => { + 42_usize + }; +} + +fn main() { + let _ok1 = 1234_i32; + let _ok2 = 1234_isize; + let _ok3 = 0x123_isize; + let _fail1 = 1234_i32; + let _fail2 = 1234_u32; + let _fail3 = 1234_isize; + let _fail4 = 1234_usize; + let _fail5 = 0x123_isize; + + let _okf1 = 1.5_f32; + let _okf2 = 1_f32; + let _failf1 = 1.5_f32; + let _failf2 = 1_f32; + + // Test for macro + let _ = lit_from_macro!(); + + // Counter example + let _ = line!(); + // Because `assert!` contains `line!()` macro. + assert_eq!(4897_u32, 32223); +} diff --git a/src/tools/clippy/tests/ui/unseparated_prefix_literals.rs b/src/tools/clippy/tests/ui/unseparated_prefix_literals.rs new file mode 100644 index 0000000000000..09608661e0ef5 --- /dev/null +++ b/src/tools/clippy/tests/ui/unseparated_prefix_literals.rs @@ -0,0 +1,41 @@ +// run-rustfix + +#![warn(clippy::unseparated_literal_suffix)] +#![allow(dead_code)] + +#[macro_use] +extern crate clippy_mini_macro_test; + +// Test for proc-macro attribute +#[derive(ClippyMiniMacroTest)] +struct Foo; + +macro_rules! lit_from_macro { + () => { + 42usize + }; +} + +fn main() { + let _ok1 = 1234_i32; + let _ok2 = 1234_isize; + let _ok3 = 0x123_isize; + let _fail1 = 1234i32; + let _fail2 = 1234u32; + let _fail3 = 1234isize; + let _fail4 = 1234usize; + let _fail5 = 0x123isize; + + let _okf1 = 1.5_f32; + let _okf2 = 1_f32; + let _failf1 = 1.5f32; + let _failf2 = 1f32; + + // Test for macro + let _ = lit_from_macro!(); + + // Counter example + let _ = line!(); + // Because `assert!` contains `line!()` macro. + assert_eq!(4897u32, 32223); +} diff --git a/src/tools/clippy/tests/ui/unseparated_prefix_literals.stderr b/src/tools/clippy/tests/ui/unseparated_prefix_literals.stderr new file mode 100644 index 0000000000000..d7dd526bcb9af --- /dev/null +++ b/src/tools/clippy/tests/ui/unseparated_prefix_literals.stderr @@ -0,0 +1,63 @@ +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:23:18 + | +LL | let _fail1 = 1234i32; + | ^^^^^^^ help: add an underscore: `1234_i32` + | + = note: `-D clippy::unseparated-literal-suffix` implied by `-D warnings` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:24:18 + | +LL | let _fail2 = 1234u32; + | ^^^^^^^ help: add an underscore: `1234_u32` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:25:18 + | +LL | let _fail3 = 1234isize; + | ^^^^^^^^^ help: add an underscore: `1234_isize` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:26:18 + | +LL | let _fail4 = 1234usize; + | ^^^^^^^^^ help: add an underscore: `1234_usize` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:27:18 + | +LL | let _fail5 = 0x123isize; + | ^^^^^^^^^^ help: add an underscore: `0x123_isize` + +error: float type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:31:19 + | +LL | let _failf1 = 1.5f32; + | ^^^^^^ help: add an underscore: `1.5_f32` + +error: float type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:32:19 + | +LL | let _failf2 = 1f32; + | ^^^^ help: add an underscore: `1_f32` + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:15:9 + | +LL | 42usize + | ^^^^^^^ help: add an underscore: `42_usize` +... +LL | let _ = lit_from_macro!(); + | ----------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: integer type suffix should be separated by an underscore + --> $DIR/unseparated_prefix_literals.rs:40:16 + | +LL | assert_eq!(4897u32, 32223); + | ^^^^^^^ help: add an underscore: `4897_u32` + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/unused_io_amount.rs b/src/tools/clippy/tests/ui/unused_io_amount.rs new file mode 100644 index 0000000000000..ebaba9629db16 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_io_amount.rs @@ -0,0 +1,25 @@ +#![allow(dead_code)] +#![warn(clippy::unused_io_amount)] + +use std::io; + +fn question_mark(s: &mut T) -> io::Result<()> { + s.write(b"test")?; + let mut buf = [0u8; 4]; + s.read(&mut buf)?; + Ok(()) +} + +fn unwrap(s: &mut T) { + s.write(b"test").unwrap(); + let mut buf = [0u8; 4]; + s.read(&mut buf).unwrap(); +} + +fn vectored(s: &mut T) -> io::Result<()> { + s.read_vectored(&mut [io::IoSliceMut::new(&mut [])])?; + s.write_vectored(&[io::IoSlice::new(&[])])?; + Ok(()) +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unused_io_amount.stderr b/src/tools/clippy/tests/ui/unused_io_amount.stderr new file mode 100644 index 0000000000000..5219d63980b4b --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_io_amount.stderr @@ -0,0 +1,40 @@ +error: written amount is not handled. Use `Write::write_all` instead + --> $DIR/unused_io_amount.rs:7:5 + | +LL | s.write(b"test")?; + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unused-io-amount` implied by `-D warnings` + +error: read amount is not handled. Use `Read::read_exact` instead + --> $DIR/unused_io_amount.rs:9:5 + | +LL | s.read(&mut buf)?; + | ^^^^^^^^^^^^^^^^^ + +error: written amount is not handled. Use `Write::write_all` instead + --> $DIR/unused_io_amount.rs:14:5 + | +LL | s.write(b"test").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: read amount is not handled. Use `Read::read_exact` instead + --> $DIR/unused_io_amount.rs:16:5 + | +LL | s.read(&mut buf).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: read amount is not handled + --> $DIR/unused_io_amount.rs:20:5 + | +LL | s.read_vectored(&mut [io::IoSliceMut::new(&mut [])])?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: written amount is not handled + --> $DIR/unused_io_amount.rs:21:5 + | +LL | s.write_vectored(&[io::IoSlice::new(&[])])?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/unused_self.rs b/src/tools/clippy/tests/ui/unused_self.rs new file mode 100644 index 0000000000000..7a4bbdda1ab27 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_self.rs @@ -0,0 +1,140 @@ +#![warn(clippy::unused_self)] +#![allow(clippy::boxed_local, clippy::fn_params_excessive_bools)] + +mod unused_self { + use std::pin::Pin; + use std::sync::{Arc, Mutex}; + + struct A {} + + impl A { + fn unused_self_move(self) {} + fn unused_self_ref(&self) {} + fn unused_self_mut_ref(&mut self) {} + fn unused_self_pin_ref(self: Pin<&Self>) {} + fn unused_self_pin_mut_ref(self: Pin<&mut Self>) {} + fn unused_self_pin_nested(self: Pin>) {} + fn unused_self_box(self: Box) {} + fn unused_with_other_used_args(&self, x: u8, y: u8) -> u8 { + x + y + } + fn unused_self_class_method(&self) { + Self::static_method(); + } + + fn static_method() {} + } +} + +mod unused_self_allow { + struct A {} + + impl A { + // shouldn't trigger + #[allow(clippy::unused_self)] + fn unused_self_move(self) {} + } + + struct B {} + + // shouldn't trigger + #[allow(clippy::unused_self)] + impl B { + fn unused_self_move(self) {} + } + + struct C {} + + #[allow(clippy::unused_self)] + impl C { + #[warn(clippy::unused_self)] + fn some_fn((): ()) {} + + // shouldn't trigger + fn unused_self_move(self) {} + } +} + +mod used_self { + use std::pin::Pin; + + struct A { + x: u8, + } + + impl A { + fn used_self_move(self) -> u8 { + self.x + } + fn used_self_ref(&self) -> u8 { + self.x + } + fn used_self_mut_ref(&mut self) { + self.x += 1 + } + fn used_self_pin_ref(self: Pin<&Self>) -> u8 { + self.x + } + fn used_self_box(self: Box) -> u8 { + self.x + } + fn used_self_with_other_unused_args(&self, x: u8, y: u8) -> u8 { + self.x + } + fn used_in_nested_closure(&self) -> u8 { + let mut a = || -> u8 { self.x }; + a() + } + + #[allow(clippy::collapsible_if)] + fn used_self_method_nested_conditions(&self, a: bool, b: bool, c: bool, d: bool) { + if a { + if b { + if c { + if d { + self.used_self_ref(); + } + } + } + } + } + + fn foo(&self) -> u32 { + let mut sum = 0u32; + for i in 0..self.x { + sum += i as u32; + } + sum + } + + fn bar(&mut self, x: u8) -> u32 { + let mut y = 0u32; + for i in 0..x { + y += self.foo() + } + y + } + } +} + +mod not_applicable { + use std::fmt; + + struct A {} + + impl fmt::Debug for A { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "A") + } + } + + impl A { + fn method(x: u8, y: u8) {} + } + + trait B { + fn method(&self) {} + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/unused_self.stderr b/src/tools/clippy/tests/ui/unused_self.stderr new file mode 100644 index 0000000000000..0534b40eabb75 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_self.stderr @@ -0,0 +1,75 @@ +error: unused `self` argument + --> $DIR/unused_self.rs:11:29 + | +LL | fn unused_self_move(self) {} + | ^^^^ + | + = note: `-D clippy::unused-self` implied by `-D warnings` + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:12:28 + | +LL | fn unused_self_ref(&self) {} + | ^^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:13:32 + | +LL | fn unused_self_mut_ref(&mut self) {} + | ^^^^^^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:14:32 + | +LL | fn unused_self_pin_ref(self: Pin<&Self>) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:15:36 + | +LL | fn unused_self_pin_mut_ref(self: Pin<&mut Self>) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:16:35 + | +LL | fn unused_self_pin_nested(self: Pin>) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:17:28 + | +LL | fn unused_self_box(self: Box) {} + | ^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:18:40 + | +LL | fn unused_with_other_used_args(&self, x: u8, y: u8) -> u8 { + | ^^^^^ + | + = help: consider refactoring to a associated function + +error: unused `self` argument + --> $DIR/unused_self.rs:21:37 + | +LL | fn unused_self_class_method(&self) { + | ^^^^^ + | + = help: consider refactoring to a associated function + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/unused_unit.fixed b/src/tools/clippy/tests/ui/unused_unit.fixed new file mode 100644 index 0000000000000..3f63624720f75 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_unit.fixed @@ -0,0 +1,59 @@ +// run-rustfix + +// The output for humans should just highlight the whole span without showing +// the suggested replacement, but we also want to test that suggested +// replacement only removes one set of parentheses, rather than naïvely +// stripping away any starting or ending parenthesis characters—hence this +// test of the JSON error format. + +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +#![deny(clippy::unused_unit)] +#![allow(dead_code)] + +struct Unitter; +impl Unitter { + // try to disorient the lint with multiple unit returns and newlines + #[allow(clippy::no_effect)] + pub fn get_unit (), G>(&self, f: F, _g: G) + where G: Fn() -> () { + let _y: &dyn Fn() -> () = &f; + (); // this should not lint, as it's not in return type position + } +} + +impl Into<()> for Unitter { + #[rustfmt::skip] + fn into(self) { + + } +} + +fn return_unit() { } + +#[allow(clippy::needless_return)] +#[allow(clippy::never_loop)] +#[allow(clippy::unit_cmp)] +fn main() { + let u = Unitter; + assert_eq!(u.get_unit(|| {}, return_unit), u.into()); + return_unit(); + loop { + break; + } + return; +} + +// https://github.com/rust-lang/rust-clippy/issues/4076 +fn foo() { + macro_rules! foo { + (recv($r:expr) -> $res:pat => $body:expr) => { + $body + } + } + + foo! { + recv(rx) -> _x => () + } +} diff --git a/src/tools/clippy/tests/ui/unused_unit.rs b/src/tools/clippy/tests/ui/unused_unit.rs new file mode 100644 index 0000000000000..8fc072ebd69f8 --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_unit.rs @@ -0,0 +1,60 @@ +// run-rustfix + +// The output for humans should just highlight the whole span without showing +// the suggested replacement, but we also want to test that suggested +// replacement only removes one set of parentheses, rather than naïvely +// stripping away any starting or ending parenthesis characters—hence this +// test of the JSON error format. + +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +#![deny(clippy::unused_unit)] +#![allow(dead_code)] + +struct Unitter; +impl Unitter { + // try to disorient the lint with multiple unit returns and newlines + #[allow(clippy::no_effect)] + pub fn get_unit (), G>(&self, f: F, _g: G) -> + () + where G: Fn() -> () { + let _y: &dyn Fn() -> () = &f; + (); // this should not lint, as it's not in return type position + } +} + +impl Into<()> for Unitter { + #[rustfmt::skip] + fn into(self) -> () { + () + } +} + +fn return_unit() -> () { () } + +#[allow(clippy::needless_return)] +#[allow(clippy::never_loop)] +#[allow(clippy::unit_cmp)] +fn main() { + let u = Unitter; + assert_eq!(u.get_unit(|| {}, return_unit), u.into()); + return_unit(); + loop { + break(); + } + return(); +} + +// https://github.com/rust-lang/rust-clippy/issues/4076 +fn foo() { + macro_rules! foo { + (recv($r:expr) -> $res:pat => $body:expr) => { + $body + } + } + + foo! { + recv(rx) -> _x => () + } +} diff --git a/src/tools/clippy/tests/ui/unused_unit.stderr b/src/tools/clippy/tests/ui/unused_unit.stderr new file mode 100644 index 0000000000000..a013d2b3495ba --- /dev/null +++ b/src/tools/clippy/tests/ui/unused_unit.stderr @@ -0,0 +1,52 @@ +error: unneeded unit return type + --> $DIR/unused_unit.rs:19:59 + | +LL | pub fn get_unit (), G>(&self, f: F, _g: G) -> + | ___________________________________________________________^ +LL | | () + | |__________^ help: remove the `-> ()` + | +note: the lint level is defined here + --> $DIR/unused_unit.rs:12:9 + | +LL | #![deny(clippy::unused_unit)] + | ^^^^^^^^^^^^^^^^^^^ + +error: unneeded unit return type + --> $DIR/unused_unit.rs:29:19 + | +LL | fn into(self) -> () { + | ^^^^^ help: remove the `-> ()` + +error: unneeded unit expression + --> $DIR/unused_unit.rs:30:9 + | +LL | () + | ^^ help: remove the final `()` + +error: unneeded unit return type + --> $DIR/unused_unit.rs:34:18 + | +LL | fn return_unit() -> () { () } + | ^^^^^ help: remove the `-> ()` + +error: unneeded unit expression + --> $DIR/unused_unit.rs:34:26 + | +LL | fn return_unit() -> () { () } + | ^^ help: remove the final `()` + +error: unneeded `()` + --> $DIR/unused_unit.rs:44:14 + | +LL | break(); + | ^^ help: remove the `()` + +error: unneeded `()` + --> $DIR/unused_unit.rs:46:11 + | +LL | return(); + | ^^ help: remove the `()` + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui/unwrap.rs b/src/tools/clippy/tests/ui/unwrap.rs new file mode 100644 index 0000000000000..fcd1fcd14d48f --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap.rs @@ -0,0 +1,16 @@ +#![warn(clippy::option_unwrap_used, clippy::result_unwrap_used)] + +fn unwrap_option() { + let opt = Some(0); + let _ = opt.unwrap(); +} + +fn unwrap_result() { + let res: Result = Ok(0); + let _ = res.unwrap(); +} + +fn main() { + unwrap_option(); + unwrap_result(); +} diff --git a/src/tools/clippy/tests/ui/unwrap.stderr b/src/tools/clippy/tests/ui/unwrap.stderr new file mode 100644 index 0000000000000..b90ce68fa97ac --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap.stderr @@ -0,0 +1,20 @@ +error: used `unwrap()` on `an Option` value + --> $DIR/unwrap.rs:5:13 + | +LL | let _ = opt.unwrap(); + | ^^^^^^^^^^^^ + | + = note: `-D clippy::option-unwrap-used` implied by `-D warnings` + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: used `unwrap()` on `a Result` value + --> $DIR/unwrap.rs:10:13 + | +LL | let _ = res.unwrap(); + | ^^^^^^^^^^^^ + | + = note: `-D clippy::result-unwrap-used` implied by `-D warnings` + = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/unwrap_or.rs b/src/tools/clippy/tests/ui/unwrap_or.rs new file mode 100644 index 0000000000000..bfb41e4394731 --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap_or.rs @@ -0,0 +1,9 @@ +#![warn(clippy::all)] + +fn main() { + let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); +} + +fn new_lines() { + let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); +} diff --git a/src/tools/clippy/tests/ui/unwrap_or.stderr b/src/tools/clippy/tests/ui/unwrap_or.stderr new file mode 100644 index 0000000000000..c3a7464fd470e --- /dev/null +++ b/src/tools/clippy/tests/ui/unwrap_or.stderr @@ -0,0 +1,16 @@ +error: use of `unwrap_or` followed by a function call + --> $DIR/unwrap_or.rs:4:47 + | +LL | let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "Fail".to_string())` + | + = note: `-D clippy::or-fun-call` implied by `-D warnings` + +error: use of `unwrap_or` followed by a function call + --> $DIR/unwrap_or.rs:8:47 + | +LL | let s = Some(String::from("test string")).unwrap_or("Fail".to_string()).len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "Fail".to_string())` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/update-all-references.sh b/src/tools/clippy/tests/ui/update-all-references.sh new file mode 100755 index 0000000000000..30ba9188db43d --- /dev/null +++ b/src/tools/clippy/tests/ui/update-all-references.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# A script to update the references for all tests. The idea is that +# you do a run, which will generate files in the build directory +# containing the (normalized) actual output of the compiler. You then +# run this script, which will copy those files over. If you find +# yourself manually editing a foo.stderr file, you're doing it wrong. +# +# See all `update-references.sh`, if you just want to update a single test. + +if [[ "$1" == "--help" || "$1" == "-h" ]]; then + echo "usage: $0" +fi + +CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-$PWD/target} +PROFILE=${PROFILE:-debug} +BUILD_DIR=${CARGO_TARGET_DIR}/${PROFILE}/test_build_base + +MY_DIR=$(dirname "$0") +cd "$MY_DIR" || exit +find . -name '*.rs' -exec ./update-references.sh "$BUILD_DIR" {} + diff --git a/src/tools/clippy/tests/ui/update-references.sh b/src/tools/clippy/tests/ui/update-references.sh new file mode 100755 index 0000000000000..2c13c327d7980 --- /dev/null +++ b/src/tools/clippy/tests/ui/update-references.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# A script to update the references for particular tests. The idea is +# that you do a run, which will generate files in the build directory +# containing the (normalized) actual output of the compiler. This +# script will then copy that output and replace the "expected output" +# files. You can then commit the changes. +# +# If you find yourself manually editing a `foo.stderr` file, you're +# doing it wrong. + +if [[ "$1" == "--help" || "$1" == "-h" || "$1" == "" || "$2" == "" ]]; then + echo "usage: $0 " + echo "" + echo "For example:" + echo " $0 ../../../build/x86_64-apple-darwin/test/ui *.rs */*.rs" +fi + +MYDIR=$(dirname "$0") + +BUILD_DIR="$1" +shift + +while [[ "$1" != "" ]]; do + STDERR_NAME="${1/%.rs/.stderr}" + STDOUT_NAME="${1/%.rs/.stdout}" + FIXED_NAME="${1/%.rs/.fixed}" + shift + if [[ -f "$BUILD_DIR"/"$STDOUT_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$STDOUT_NAME" "$MYDIR"/"$STDOUT_NAME"); then + echo updating "$MYDIR"/"$STDOUT_NAME" + cp "$BUILD_DIR"/"$STDOUT_NAME" "$MYDIR"/"$STDOUT_NAME" + fi + if [[ -f "$BUILD_DIR"/"$STDERR_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$STDERR_NAME" "$MYDIR"/"$STDERR_NAME"); then + echo updating "$MYDIR"/"$STDERR_NAME" + cp "$BUILD_DIR"/"$STDERR_NAME" "$MYDIR"/"$STDERR_NAME" + fi + if [[ -f "$BUILD_DIR"/"$FIXED_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$FIXED_NAME" "$MYDIR"/"$FIXED_NAME"); then + echo updating "$MYDIR"/"$FIXED_NAME" + cp "$BUILD_DIR"/"$FIXED_NAME" "$MYDIR"/"$FIXED_NAME" + fi +done diff --git a/src/tools/clippy/tests/ui/use_self.fixed b/src/tools/clippy/tests/ui/use_self.fixed new file mode 100644 index 0000000000000..ebb3aa28daf3d --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self.fixed @@ -0,0 +1,253 @@ +// run-rustfix +// edition:2018 + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait)] + +fn main() {} + +mod use_self { + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + fn test() -> Self { + Self::new() + } + } + + impl Default for Foo { + fn default() -> Self { + Self::new() + } + } +} + +mod better { + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + fn test() -> Self { + Self::new() + } + } + + impl Default for Foo { + fn default() -> Self { + Self::new() + } + } +} + +mod lifetimes { + struct Foo<'a> { + foo_str: &'a str, + } + + impl<'a> Foo<'a> { + // Cannot use `Self` as return type, because the function is actually `fn foo<'b>(s: &'b str) -> + // Foo<'b>` + fn foo(s: &str) -> Foo { + Foo { foo_str: s } + } + // cannot replace with `Self`, because that's `Foo<'a>` + fn bar() -> Foo<'static> { + Foo { foo_str: "foo" } + } + + // FIXME: the lint does not handle lifetimed struct + // `Self` should be applicable here + fn clone(&self) -> Foo<'a> { + Foo { foo_str: self.foo_str } + } + } +} + +mod issue2894 { + trait IntoBytes { + fn into_bytes(&self) -> Vec; + } + + // This should not be linted + impl IntoBytes for u8 { + fn into_bytes(&self) -> Vec { + vec![*self] + } + } +} + +mod existential { + struct Foo; + + impl Foo { + fn bad(foos: &[Self]) -> impl Iterator { + foos.iter() + } + + fn good(foos: &[Self]) -> impl Iterator { + foos.iter() + } + } +} + +mod tuple_structs { + pub struct TS(i32); + + impl TS { + pub fn ts() -> Self { + Self(0) + } + } +} + +mod macros { + macro_rules! use_self_expand { + () => { + fn new() -> Self { + Self {} + } + }; + } + + struct Foo {} + + impl Foo { + use_self_expand!(); // Should lint in local macros + } +} + +mod nesting { + struct Foo {} + impl Foo { + fn foo() { + #[allow(unused_imports)] + use self::Foo; // Can't use Self here + struct Bar { + foo: Foo, // Foo != Self + } + + impl Bar { + fn bar() -> Self { + Self { foo: Foo {} } + } + } + + // Can't use Self here + fn baz() -> Foo { + Foo {} + } + } + + // Should lint here + fn baz() -> Self { + Self {} + } + } + + enum Enum { + A, + B(u64), + C { field: bool }, + } + impl Enum { + fn method() { + #[allow(unused_imports)] + use self::Enum::*; // Issue 3425 + static STATIC: Enum = Enum::A; // Can't use Self as type + } + + fn method2() { + let _ = Self::B(42); + let _ = Self::C { field: true }; + let _ = Self::A; + } + } +} + +mod issue3410 { + + struct A; + struct B; + + trait Trait { + fn a(v: T); + } + + impl Trait> for Vec { + fn a(_: Vec) {} + } +} + +#[allow(clippy::no_effect, path_statements)] +mod rustfix { + mod nested { + pub struct A {} + } + + impl nested::A { + const A: bool = true; + + fn fun_1() {} + + fn fun_2() { + Self::fun_1(); + Self::A; + + Self {}; + } + } +} + +mod issue3567 { + struct TestStruct {} + impl TestStruct { + fn from_something() -> Self { + Self {} + } + } + + trait Test { + fn test() -> TestStruct; + } + + impl Test for TestStruct { + fn test() -> TestStruct { + Self::from_something() + } + } +} + +mod paths_created_by_lowering { + use std::ops::Range; + + struct S {} + + impl S { + const A: usize = 0; + const B: usize = 1; + + async fn g() -> Self { + Self {} + } + + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[Self::A..Self::B] + } + } + + trait T { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8]; + } + + impl T for Range { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[0..1] + } + } +} diff --git a/src/tools/clippy/tests/ui/use_self.rs b/src/tools/clippy/tests/ui/use_self.rs new file mode 100644 index 0000000000000..8a182192ab34d --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self.rs @@ -0,0 +1,253 @@ +// run-rustfix +// edition:2018 + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait)] + +fn main() {} + +mod use_self { + struct Foo {} + + impl Foo { + fn new() -> Foo { + Foo {} + } + fn test() -> Foo { + Foo::new() + } + } + + impl Default for Foo { + fn default() -> Foo { + Foo::new() + } + } +} + +mod better { + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + fn test() -> Self { + Self::new() + } + } + + impl Default for Foo { + fn default() -> Self { + Self::new() + } + } +} + +mod lifetimes { + struct Foo<'a> { + foo_str: &'a str, + } + + impl<'a> Foo<'a> { + // Cannot use `Self` as return type, because the function is actually `fn foo<'b>(s: &'b str) -> + // Foo<'b>` + fn foo(s: &str) -> Foo { + Foo { foo_str: s } + } + // cannot replace with `Self`, because that's `Foo<'a>` + fn bar() -> Foo<'static> { + Foo { foo_str: "foo" } + } + + // FIXME: the lint does not handle lifetimed struct + // `Self` should be applicable here + fn clone(&self) -> Foo<'a> { + Foo { foo_str: self.foo_str } + } + } +} + +mod issue2894 { + trait IntoBytes { + fn into_bytes(&self) -> Vec; + } + + // This should not be linted + impl IntoBytes for u8 { + fn into_bytes(&self) -> Vec { + vec![*self] + } + } +} + +mod existential { + struct Foo; + + impl Foo { + fn bad(foos: &[Self]) -> impl Iterator { + foos.iter() + } + + fn good(foos: &[Self]) -> impl Iterator { + foos.iter() + } + } +} + +mod tuple_structs { + pub struct TS(i32); + + impl TS { + pub fn ts() -> Self { + TS(0) + } + } +} + +mod macros { + macro_rules! use_self_expand { + () => { + fn new() -> Foo { + Foo {} + } + }; + } + + struct Foo {} + + impl Foo { + use_self_expand!(); // Should lint in local macros + } +} + +mod nesting { + struct Foo {} + impl Foo { + fn foo() { + #[allow(unused_imports)] + use self::Foo; // Can't use Self here + struct Bar { + foo: Foo, // Foo != Self + } + + impl Bar { + fn bar() -> Bar { + Bar { foo: Foo {} } + } + } + + // Can't use Self here + fn baz() -> Foo { + Foo {} + } + } + + // Should lint here + fn baz() -> Foo { + Foo {} + } + } + + enum Enum { + A, + B(u64), + C { field: bool }, + } + impl Enum { + fn method() { + #[allow(unused_imports)] + use self::Enum::*; // Issue 3425 + static STATIC: Enum = Enum::A; // Can't use Self as type + } + + fn method2() { + let _ = Enum::B(42); + let _ = Enum::C { field: true }; + let _ = Enum::A; + } + } +} + +mod issue3410 { + + struct A; + struct B; + + trait Trait { + fn a(v: T); + } + + impl Trait> for Vec { + fn a(_: Vec) {} + } +} + +#[allow(clippy::no_effect, path_statements)] +mod rustfix { + mod nested { + pub struct A {} + } + + impl nested::A { + const A: bool = true; + + fn fun_1() {} + + fn fun_2() { + nested::A::fun_1(); + nested::A::A; + + nested::A {}; + } + } +} + +mod issue3567 { + struct TestStruct {} + impl TestStruct { + fn from_something() -> Self { + Self {} + } + } + + trait Test { + fn test() -> TestStruct; + } + + impl Test for TestStruct { + fn test() -> TestStruct { + TestStruct::from_something() + } + } +} + +mod paths_created_by_lowering { + use std::ops::Range; + + struct S {} + + impl S { + const A: usize = 0; + const B: usize = 1; + + async fn g() -> S { + S {} + } + + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[S::A..S::B] + } + } + + trait T { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8]; + } + + impl T for Range { + fn f<'a>(&self, p: &'a [u8]) -> &'a [u8] { + &p[0..1] + } + } +} diff --git a/src/tools/clippy/tests/ui/use_self.stderr b/src/tools/clippy/tests/ui/use_self.stderr new file mode 100644 index 0000000000000..b33928597c145 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self.stderr @@ -0,0 +1,164 @@ +error: unnecessary structure name repetition + --> $DIR/use_self.rs:14:21 + | +LL | fn new() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:15:13 + | +LL | Foo {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:17:22 + | +LL | fn test() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:18:13 + | +LL | Foo::new() + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:23:25 + | +LL | fn default() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:24:13 + | +LL | Foo::new() + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:89:56 + | +LL | fn bad(foos: &[Self]) -> impl Iterator { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:104:13 + | +LL | TS(0) + | ^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:112:25 + | +LL | fn new() -> Foo { + | ^^^ help: use the applicable keyword: `Self` +... +LL | use_self_expand!(); // Should lint in local macros + | ------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:113:17 + | +LL | Foo {} + | ^^^ help: use the applicable keyword: `Self` +... +LL | use_self_expand!(); // Should lint in local macros + | ------------------- in this macro invocation + | + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:148:21 + | +LL | fn baz() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:149:13 + | +LL | Foo {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:136:29 + | +LL | fn bar() -> Bar { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:137:21 + | +LL | Bar { foo: Foo {} } + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:166:21 + | +LL | let _ = Enum::B(42); + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:167:21 + | +LL | let _ = Enum::C { field: true }; + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:168:21 + | +LL | let _ = Enum::A; + | ^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:199:13 + | +LL | nested::A::fun_1(); + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:200:13 + | +LL | nested::A::A; + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:202:13 + | +LL | nested::A {}; + | ^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:221:13 + | +LL | TestStruct::from_something() + | ^^^^^^^^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:235:25 + | +LL | async fn g() -> S { + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:236:13 + | +LL | S {} + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:240:16 + | +LL | &p[S::A..S::B] + | ^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self.rs:240:22 + | +LL | &p[S::A..S::B] + | ^ help: use the applicable keyword: `Self` + +error: aborting due to 25 previous errors + diff --git a/src/tools/clippy/tests/ui/use_self_trait.fixed b/src/tools/clippy/tests/ui/use_self_trait.fixed new file mode 100644 index 0000000000000..1582ae114bf4c --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self_trait.fixed @@ -0,0 +1,114 @@ +// run-rustfix + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait, clippy::boxed_local)] + +use std::ops::Mul; + +trait SelfTrait { + fn refs(p1: &Self) -> &Self; + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self; + fn mut_refs(p1: &mut Self) -> &mut Self; + fn nested(p1: Box, p2: (&u8, &Self)); + fn vals(r: Self) -> Self; +} + +#[derive(Default)] +struct Bad; + +impl SelfTrait for Bad { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +impl Mul for Bad { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + rhs + } +} + +impl Clone for Bad { + fn clone(&self) -> Self { + Self + } +} + +#[derive(Default)] +struct Good; + +impl SelfTrait for Good { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +impl Mul for Good { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + rhs + } +} + +trait NameTrait { + fn refs(p1: &u8) -> &u8; + fn ref_refs<'a>(p1: &'a &'a u8) -> &'a &'a u8; + fn mut_refs(p1: &mut u8) -> &mut u8; + fn nested(p1: Box, p2: (&u8, &u8)); + fn vals(p1: u8) -> u8; +} + +// Using `Self` instead of the type name is OK +impl NameTrait for u8 { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&Self, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/use_self_trait.rs b/src/tools/clippy/tests/ui/use_self_trait.rs new file mode 100644 index 0000000000000..70667b9797e76 --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self_trait.rs @@ -0,0 +1,114 @@ +// run-rustfix + +#![warn(clippy::use_self)] +#![allow(dead_code)] +#![allow(clippy::should_implement_trait, clippy::boxed_local)] + +use std::ops::Mul; + +trait SelfTrait { + fn refs(p1: &Self) -> &Self; + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self; + fn mut_refs(p1: &mut Self) -> &mut Self; + fn nested(p1: Box, p2: (&u8, &Self)); + fn vals(r: Self) -> Self; +} + +#[derive(Default)] +struct Bad; + +impl SelfTrait for Bad { + fn refs(p1: &Bad) -> &Bad { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad { + p1 + } + + fn mut_refs(p1: &mut Bad) -> &mut Bad { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Bad)) {} + + fn vals(_: Bad) -> Bad { + Bad::default() + } +} + +impl Mul for Bad { + type Output = Bad; + + fn mul(self, rhs: Bad) -> Bad { + rhs + } +} + +impl Clone for Bad { + fn clone(&self) -> Self { + Bad + } +} + +#[derive(Default)] +struct Good; + +impl SelfTrait for Good { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&u8, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +impl Mul for Good { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + rhs + } +} + +trait NameTrait { + fn refs(p1: &u8) -> &u8; + fn ref_refs<'a>(p1: &'a &'a u8) -> &'a &'a u8; + fn mut_refs(p1: &mut u8) -> &mut u8; + fn nested(p1: Box, p2: (&u8, &u8)); + fn vals(p1: u8) -> u8; +} + +// Using `Self` instead of the type name is OK +impl NameTrait for u8 { + fn refs(p1: &Self) -> &Self { + p1 + } + + fn ref_refs<'a>(p1: &'a &'a Self) -> &'a &'a Self { + p1 + } + + fn mut_refs(p1: &mut Self) -> &mut Self { + p1 + } + + fn nested(_p1: Box, _p2: (&Self, &Self)) {} + + fn vals(_: Self) -> Self { + Self::default() + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/use_self_trait.stderr b/src/tools/clippy/tests/ui/use_self_trait.stderr new file mode 100644 index 0000000000000..4f2506cc1192f --- /dev/null +++ b/src/tools/clippy/tests/ui/use_self_trait.stderr @@ -0,0 +1,94 @@ +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:21:18 + | +LL | fn refs(p1: &Bad) -> &Bad { + | ^^^ help: use the applicable keyword: `Self` + | + = note: `-D clippy::use-self` implied by `-D warnings` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:21:27 + | +LL | fn refs(p1: &Bad) -> &Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:25:33 + | +LL | fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:25:49 + | +LL | fn ref_refs<'a>(p1: &'a &'a Bad) -> &'a &'a Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:29:26 + | +LL | fn mut_refs(p1: &mut Bad) -> &mut Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:29:39 + | +LL | fn mut_refs(p1: &mut Bad) -> &mut Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:33:24 + | +LL | fn nested(_p1: Box, _p2: (&u8, &Bad)) {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:33:42 + | +LL | fn nested(_p1: Box, _p2: (&u8, &Bad)) {} + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:35:16 + | +LL | fn vals(_: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:35:24 + | +LL | fn vals(_: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:36:9 + | +LL | Bad::default() + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:41:19 + | +LL | type Output = Bad; + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:43:23 + | +LL | fn mul(self, rhs: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:43:31 + | +LL | fn mul(self, rhs: Bad) -> Bad { + | ^^^ help: use the applicable keyword: `Self` + +error: unnecessary structure name repetition + --> $DIR/use_self_trait.rs:50:9 + | +LL | Bad + | ^^^ help: use the applicable keyword: `Self` + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/used_underscore_binding.rs b/src/tools/clippy/tests/ui/used_underscore_binding.rs new file mode 100644 index 0000000000000..8e0243c49aaa0 --- /dev/null +++ b/src/tools/clippy/tests/ui/used_underscore_binding.rs @@ -0,0 +1,119 @@ +// edition:2018 +// aux-build:proc_macro_derive.rs + +#![feature(rustc_private)] +#![warn(clippy::all)] +#![allow(clippy::blacklisted_name)] +#![warn(clippy::used_underscore_binding)] + +#[macro_use] +extern crate proc_macro_derive; + +// This should not trigger the lint. There's underscore binding inside the external derive that +// would trigger the `used_underscore_binding` lint. +#[derive(DeriveSomething)] +struct Baz; + +macro_rules! test_macro { + () => {{ + let _foo = 42; + _foo + 1 + }}; +} + +/// Tests that we lint if we use a binding with a single leading underscore +fn prefix_underscore(_foo: u32) -> u32 { + _foo + 1 +} + +/// Tests that we lint if we use a `_`-variable defined outside within a macro expansion +fn in_macro_or_desugar(_foo: u32) { + println!("{}", _foo); + assert_eq!(_foo, _foo); + + test_macro!() + 1; +} + +// Struct for testing use of fields prefixed with an underscore +struct StructFieldTest { + _underscore_field: u32, +} + +/// Tests that we lint the use of a struct field which is prefixed with an underscore +fn in_struct_field() { + let mut s = StructFieldTest { _underscore_field: 0 }; + s._underscore_field += 1; +} + +/// Tests that we do not lint if the underscore is not a prefix +fn non_prefix_underscore(some_foo: u32) -> u32 { + some_foo + 1 +} + +/// Tests that we do not lint if we do not use the binding (simple case) +fn unused_underscore_simple(_foo: u32) -> u32 { + 1 +} + +/// Tests that we do not lint if we do not use the binding (complex case). This checks for +/// compatibility with the built-in `unused_variables` lint. +fn unused_underscore_complex(mut _foo: u32) -> u32 { + _foo += 1; + _foo = 2; + 1 +} + +/// Test that we do not lint for multiple underscores +fn multiple_underscores(__foo: u32) -> u32 { + __foo + 1 +} + +// Non-variable bindings with preceding underscore +fn _fn_test() {} +struct _StructTest; +enum _EnumTest { + _Empty, + _Value(_StructTest), +} + +/// Tests that we do not lint for non-variable bindings +fn non_variables() { + _fn_test(); + let _s = _StructTest; + let _e = match _EnumTest::_Value(_StructTest) { + _EnumTest::_Empty => 0, + _EnumTest::_Value(_st) => 1, + }; + let f = _fn_test; + f(); +} + +// Tests that we do not lint if the binding comes from await desugaring, +// but we do lint the awaited expression. See issue 5360. +async fn await_desugaring() { + async fn foo() {} + fn uses_i(_i: i32) {} + + foo().await; + ({ + let _i = 5; + uses_i(_i); + foo() + }) + .await +} + +fn main() { + let foo = 0u32; + // tests of unused_underscore lint + let _ = prefix_underscore(foo); + in_macro_or_desugar(foo); + in_struct_field(); + // possible false positives + let _ = non_prefix_underscore(foo); + let _ = unused_underscore_simple(foo); + let _ = unused_underscore_complex(foo); + let _ = multiple_underscores(foo); + non_variables(); + await_desugaring(); +} diff --git a/src/tools/clippy/tests/ui/used_underscore_binding.stderr b/src/tools/clippy/tests/ui/used_underscore_binding.stderr new file mode 100644 index 0000000000000..68e96148093d2 --- /dev/null +++ b/src/tools/clippy/tests/ui/used_underscore_binding.stderr @@ -0,0 +1,40 @@ +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used. + --> $DIR/used_underscore_binding.rs:26:5 + | +LL | _foo + 1 + | ^^^^ + | + = note: `-D clippy::used-underscore-binding` implied by `-D warnings` + +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used. + --> $DIR/used_underscore_binding.rs:31:20 + | +LL | println!("{}", _foo); + | ^^^^ + +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used. + --> $DIR/used_underscore_binding.rs:32:16 + | +LL | assert_eq!(_foo, _foo); + | ^^^^ + +error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used. + --> $DIR/used_underscore_binding.rs:32:22 + | +LL | assert_eq!(_foo, _foo); + | ^^^^ + +error: used binding `_underscore_field` which is prefixed with an underscore. A leading underscore signals that a binding will not be used. + --> $DIR/used_underscore_binding.rs:45:5 + | +LL | s._underscore_field += 1; + | ^^^^^^^^^^^^^^^^^^^ + +error: used binding `_i` which is prefixed with an underscore. A leading underscore signals that a binding will not be used. + --> $DIR/used_underscore_binding.rs:100:16 + | +LL | uses_i(_i); + | ^^ + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/useful_asref.rs b/src/tools/clippy/tests/ui/useful_asref.rs new file mode 100644 index 0000000000000..a9f0170a79cd9 --- /dev/null +++ b/src/tools/clippy/tests/ui/useful_asref.rs @@ -0,0 +1,13 @@ +#![deny(clippy::useless_asref)] + +trait Trait { + fn as_ptr(&self); +} + +impl<'a> Trait for &'a [u8] { + fn as_ptr(&self) { + self.as_ref().as_ptr(); + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/useless_asref.fixed b/src/tools/clippy/tests/ui/useless_asref.fixed new file mode 100644 index 0000000000000..e356f13d087b1 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_asref.fixed @@ -0,0 +1,135 @@ +// run-rustfix + +#![deny(clippy::useless_asref)] + +use std::fmt::Debug; + +struct FakeAsRef; + +#[allow(clippy::should_implement_trait)] +impl FakeAsRef { + fn as_ref(&self) -> &Self { + self + } +} + +struct MoreRef; + +impl<'a, 'b, 'c> AsRef<&'a &'b &'c MoreRef> for MoreRef { + fn as_ref(&self) -> &&'a &'b &'c MoreRef { + &&&&MoreRef + } +} + +fn foo_rstr(x: &str) { + println!("{:?}", x); +} +fn foo_rslice(x: &[i32]) { + println!("{:?}", x); +} +fn foo_mrslice(x: &mut [i32]) { + println!("{:?}", x); +} +fn foo_rrrrmr(_: &&&&MoreRef) { + println!("so many refs"); +} + +fn not_ok() { + let rstr: &str = "hello"; + let mut mrslice: &mut [i32] = &mut [1, 2, 3]; + + { + let rslice: &[i32] = &*mrslice; + foo_rstr(rstr); + foo_rstr(rstr); + foo_rslice(rslice); + foo_rslice(rslice); + } + { + foo_mrslice(mrslice); + foo_mrslice(mrslice); + foo_rslice(mrslice); + foo_rslice(mrslice); + } + + { + let rrrrrstr = &&&&rstr; + let rrrrrslice = &&&&&*mrslice; + foo_rslice(rrrrrslice); + foo_rslice(rrrrrslice); + foo_rstr(rrrrrstr); + foo_rstr(rrrrrstr); + } + { + let mrrrrrslice = &mut &mut &mut &mut mrslice; + foo_mrslice(mrrrrrslice); + foo_mrslice(mrrrrrslice); + foo_rslice(mrrrrrslice); + foo_rslice(mrrrrrslice); + } + #[allow(unused_parens, clippy::double_parens)] + foo_rrrrmr((&&&&MoreRef)); + + generic_not_ok(mrslice); + generic_ok(mrslice); +} + +fn ok() { + let string = "hello".to_owned(); + let mut arr = [1, 2, 3]; + let mut vec = vec![1, 2, 3]; + + { + foo_rstr(string.as_ref()); + foo_rslice(arr.as_ref()); + foo_rslice(vec.as_ref()); + } + { + foo_mrslice(arr.as_mut()); + foo_mrslice(vec.as_mut()); + } + + { + let rrrrstring = &&&&string; + let rrrrarr = &&&&arr; + let rrrrvec = &&&&vec; + foo_rstr(rrrrstring.as_ref()); + foo_rslice(rrrrarr.as_ref()); + foo_rslice(rrrrvec.as_ref()); + } + { + let mrrrrarr = &mut &mut &mut &mut arr; + let mrrrrvec = &mut &mut &mut &mut vec; + foo_mrslice(mrrrrarr.as_mut()); + foo_mrslice(mrrrrvec.as_mut()); + } + FakeAsRef.as_ref(); + foo_rrrrmr(MoreRef.as_ref()); + + generic_not_ok(arr.as_mut()); + generic_ok(&mut arr); +} + +fn foo_mrt(t: &mut T) { + println!("{:?}", t); +} +fn foo_rt(t: &T) { + println!("{:?}", t); +} + +fn generic_not_ok + AsRef + Debug + ?Sized>(mrt: &mut T) { + foo_mrt(mrt); + foo_mrt(mrt); + foo_rt(mrt); + foo_rt(mrt); +} + +fn generic_ok + AsRef + ?Sized, T: Debug + ?Sized>(mru: &mut U) { + foo_mrt(mru.as_mut()); + foo_rt(mru.as_ref()); +} + +fn main() { + not_ok(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/useless_asref.rs b/src/tools/clippy/tests/ui/useless_asref.rs new file mode 100644 index 0000000000000..2a80291f5d837 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_asref.rs @@ -0,0 +1,135 @@ +// run-rustfix + +#![deny(clippy::useless_asref)] + +use std::fmt::Debug; + +struct FakeAsRef; + +#[allow(clippy::should_implement_trait)] +impl FakeAsRef { + fn as_ref(&self) -> &Self { + self + } +} + +struct MoreRef; + +impl<'a, 'b, 'c> AsRef<&'a &'b &'c MoreRef> for MoreRef { + fn as_ref(&self) -> &&'a &'b &'c MoreRef { + &&&&MoreRef + } +} + +fn foo_rstr(x: &str) { + println!("{:?}", x); +} +fn foo_rslice(x: &[i32]) { + println!("{:?}", x); +} +fn foo_mrslice(x: &mut [i32]) { + println!("{:?}", x); +} +fn foo_rrrrmr(_: &&&&MoreRef) { + println!("so many refs"); +} + +fn not_ok() { + let rstr: &str = "hello"; + let mut mrslice: &mut [i32] = &mut [1, 2, 3]; + + { + let rslice: &[i32] = &*mrslice; + foo_rstr(rstr.as_ref()); + foo_rstr(rstr); + foo_rslice(rslice.as_ref()); + foo_rslice(rslice); + } + { + foo_mrslice(mrslice.as_mut()); + foo_mrslice(mrslice); + foo_rslice(mrslice.as_ref()); + foo_rslice(mrslice); + } + + { + let rrrrrstr = &&&&rstr; + let rrrrrslice = &&&&&*mrslice; + foo_rslice(rrrrrslice.as_ref()); + foo_rslice(rrrrrslice); + foo_rstr(rrrrrstr.as_ref()); + foo_rstr(rrrrrstr); + } + { + let mrrrrrslice = &mut &mut &mut &mut mrslice; + foo_mrslice(mrrrrrslice.as_mut()); + foo_mrslice(mrrrrrslice); + foo_rslice(mrrrrrslice.as_ref()); + foo_rslice(mrrrrrslice); + } + #[allow(unused_parens, clippy::double_parens)] + foo_rrrrmr((&&&&MoreRef).as_ref()); + + generic_not_ok(mrslice); + generic_ok(mrslice); +} + +fn ok() { + let string = "hello".to_owned(); + let mut arr = [1, 2, 3]; + let mut vec = vec![1, 2, 3]; + + { + foo_rstr(string.as_ref()); + foo_rslice(arr.as_ref()); + foo_rslice(vec.as_ref()); + } + { + foo_mrslice(arr.as_mut()); + foo_mrslice(vec.as_mut()); + } + + { + let rrrrstring = &&&&string; + let rrrrarr = &&&&arr; + let rrrrvec = &&&&vec; + foo_rstr(rrrrstring.as_ref()); + foo_rslice(rrrrarr.as_ref()); + foo_rslice(rrrrvec.as_ref()); + } + { + let mrrrrarr = &mut &mut &mut &mut arr; + let mrrrrvec = &mut &mut &mut &mut vec; + foo_mrslice(mrrrrarr.as_mut()); + foo_mrslice(mrrrrvec.as_mut()); + } + FakeAsRef.as_ref(); + foo_rrrrmr(MoreRef.as_ref()); + + generic_not_ok(arr.as_mut()); + generic_ok(&mut arr); +} + +fn foo_mrt(t: &mut T) { + println!("{:?}", t); +} +fn foo_rt(t: &T) { + println!("{:?}", t); +} + +fn generic_not_ok + AsRef + Debug + ?Sized>(mrt: &mut T) { + foo_mrt(mrt.as_mut()); + foo_mrt(mrt); + foo_rt(mrt.as_ref()); + foo_rt(mrt); +} + +fn generic_ok + AsRef + ?Sized, T: Debug + ?Sized>(mru: &mut U) { + foo_mrt(mru.as_mut()); + foo_rt(mru.as_ref()); +} + +fn main() { + not_ok(); + ok(); +} diff --git a/src/tools/clippy/tests/ui/useless_asref.stderr b/src/tools/clippy/tests/ui/useless_asref.stderr new file mode 100644 index 0000000000000..5876b54aca8f0 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_asref.stderr @@ -0,0 +1,74 @@ +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:43:18 + | +LL | foo_rstr(rstr.as_ref()); + | ^^^^^^^^^^^^^ help: try this: `rstr` + | +note: the lint level is defined here + --> $DIR/useless_asref.rs:3:9 + | +LL | #![deny(clippy::useless_asref)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:45:20 + | +LL | foo_rslice(rslice.as_ref()); + | ^^^^^^^^^^^^^^^ help: try this: `rslice` + +error: this call to `as_mut` does nothing + --> $DIR/useless_asref.rs:49:21 + | +LL | foo_mrslice(mrslice.as_mut()); + | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:51:20 + | +LL | foo_rslice(mrslice.as_ref()); + | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:58:20 + | +LL | foo_rslice(rrrrrslice.as_ref()); + | ^^^^^^^^^^^^^^^^^^^ help: try this: `rrrrrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:60:18 + | +LL | foo_rstr(rrrrrstr.as_ref()); + | ^^^^^^^^^^^^^^^^^ help: try this: `rrrrrstr` + +error: this call to `as_mut` does nothing + --> $DIR/useless_asref.rs:65:21 + | +LL | foo_mrslice(mrrrrrslice.as_mut()); + | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:67:20 + | +LL | foo_rslice(mrrrrrslice.as_ref()); + | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:71:16 + | +LL | foo_rrrrmr((&&&&MoreRef).as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(&&&&MoreRef)` + +error: this call to `as_mut` does nothing + --> $DIR/useless_asref.rs:121:13 + | +LL | foo_mrt(mrt.as_mut()); + | ^^^^^^^^^^^^ help: try this: `mrt` + +error: this call to `as_ref` does nothing + --> $DIR/useless_asref.rs:123:12 + | +LL | foo_rt(mrt.as_ref()); + | ^^^^^^^^^^^^ help: try this: `mrt` + +error: aborting due to 11 previous errors + diff --git a/src/tools/clippy/tests/ui/useless_attribute.fixed b/src/tools/clippy/tests/ui/useless_attribute.fixed new file mode 100644 index 0000000000000..b222e2f7976d5 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_attribute.fixed @@ -0,0 +1,61 @@ +// run-rustfix +// aux-build:proc_macro_derive.rs + +#![warn(clippy::useless_attribute)] +#![warn(unreachable_pub)] +#![feature(rustc_private)] + +#![allow(dead_code)] +#![cfg_attr(feature = "cargo-clippy", allow(dead_code))] +#[rustfmt::skip] +#[allow(unused_imports)] +#[allow(unused_extern_crates)] +#[macro_use] +extern crate rustc_middle; + +#[macro_use] +extern crate proc_macro_derive; + +// don't lint on unused_import for `use` items +#[allow(unused_imports)] +use std::collections; + +// don't lint on unused for `use` items +#[allow(unused)] +use std::option; + +// don't lint on deprecated for `use` items +mod foo { + #[deprecated] + pub struct Bar; +} +#[allow(deprecated)] +pub use foo::Bar; + +// This should not trigger the lint. There's lint level definitions inside the external derive +// that would trigger the useless_attribute lint. +#[derive(DeriveSomething)] +struct Baz; + +// don't lint on unreachable_pub for `use` items +mod a { + mod b { + #[allow(dead_code)] + #[allow(unreachable_pub)] + pub struct C {} + } + + #[allow(unreachable_pub)] + pub use self::b::C; +} + +fn test_indented_attr() { + #![allow(clippy::almost_swapped)] + use std::collections::HashSet; + + let _ = HashSet::::default(); +} + +fn main() { + test_indented_attr(); +} diff --git a/src/tools/clippy/tests/ui/useless_attribute.rs b/src/tools/clippy/tests/ui/useless_attribute.rs new file mode 100644 index 0000000000000..3422eace4ab97 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_attribute.rs @@ -0,0 +1,61 @@ +// run-rustfix +// aux-build:proc_macro_derive.rs + +#![warn(clippy::useless_attribute)] +#![warn(unreachable_pub)] +#![feature(rustc_private)] + +#[allow(dead_code)] +#[cfg_attr(feature = "cargo-clippy", allow(dead_code))] +#[rustfmt::skip] +#[allow(unused_imports)] +#[allow(unused_extern_crates)] +#[macro_use] +extern crate rustc_middle; + +#[macro_use] +extern crate proc_macro_derive; + +// don't lint on unused_import for `use` items +#[allow(unused_imports)] +use std::collections; + +// don't lint on unused for `use` items +#[allow(unused)] +use std::option; + +// don't lint on deprecated for `use` items +mod foo { + #[deprecated] + pub struct Bar; +} +#[allow(deprecated)] +pub use foo::Bar; + +// This should not trigger the lint. There's lint level definitions inside the external derive +// that would trigger the useless_attribute lint. +#[derive(DeriveSomething)] +struct Baz; + +// don't lint on unreachable_pub for `use` items +mod a { + mod b { + #[allow(dead_code)] + #[allow(unreachable_pub)] + pub struct C {} + } + + #[allow(unreachable_pub)] + pub use self::b::C; +} + +fn test_indented_attr() { + #[allow(clippy::almost_swapped)] + use std::collections::HashSet; + + let _ = HashSet::::default(); +} + +fn main() { + test_indented_attr(); +} diff --git a/src/tools/clippy/tests/ui/useless_attribute.stderr b/src/tools/clippy/tests/ui/useless_attribute.stderr new file mode 100644 index 0000000000000..57ba976730c17 --- /dev/null +++ b/src/tools/clippy/tests/ui/useless_attribute.stderr @@ -0,0 +1,22 @@ +error: useless lint attribute + --> $DIR/useless_attribute.rs:8:1 + | +LL | #[allow(dead_code)] + | ^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(dead_code)]` + | + = note: `-D clippy::useless-attribute` implied by `-D warnings` + +error: useless lint attribute + --> $DIR/useless_attribute.rs:9:1 + | +LL | #[cfg_attr(feature = "cargo-clippy", allow(dead_code))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![cfg_attr(feature = "cargo-clippy", allow(dead_code)` + +error: useless lint attribute + --> $DIR/useless_attribute.rs:53:5 + | +LL | #[allow(clippy::almost_swapped)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if you just forgot a `!`, use: `#![allow(clippy::almost_swapped)]` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/vec.fixed b/src/tools/clippy/tests/ui/vec.fixed new file mode 100644 index 0000000000000..e73a791891f89 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec.fixed @@ -0,0 +1,55 @@ +// run-rustfix + +#![warn(clippy::useless_vec)] + +#[derive(Debug)] +struct NonCopy; + +fn on_slice(_: &[u8]) {} +#[allow(clippy::ptr_arg)] +fn on_vec(_: &Vec) {} + +struct Line { + length: usize, +} + +impl Line { + fn length(&self) -> usize { + self.length + } +} + +fn main() { + on_slice(&[]); + on_slice(&[]); + + on_slice(&[1, 2]); + on_slice(&[1, 2]); + + on_slice(&[1, 2]); + on_slice(&[1, 2]); + #[rustfmt::skip] + on_slice(&[1, 2]); + on_slice(&[1, 2]); + + on_slice(&[1; 2]); + on_slice(&[1; 2]); + + on_vec(&vec![]); + on_vec(&vec![1, 2]); + on_vec(&vec![1; 2]); + + // Now with non-constant expressions + let line = Line { length: 2 }; + + on_slice(&vec![2; line.length]); + on_slice(&vec![2; line.length()]); + + for a in &[1, 2, 3] { + println!("{:?}", a); + } + + for a in vec![NonCopy, NonCopy] { + println!("{:?}", a); + } +} diff --git a/src/tools/clippy/tests/ui/vec.rs b/src/tools/clippy/tests/ui/vec.rs new file mode 100644 index 0000000000000..3eb960f53d7af --- /dev/null +++ b/src/tools/clippy/tests/ui/vec.rs @@ -0,0 +1,55 @@ +// run-rustfix + +#![warn(clippy::useless_vec)] + +#[derive(Debug)] +struct NonCopy; + +fn on_slice(_: &[u8]) {} +#[allow(clippy::ptr_arg)] +fn on_vec(_: &Vec) {} + +struct Line { + length: usize, +} + +impl Line { + fn length(&self) -> usize { + self.length + } +} + +fn main() { + on_slice(&vec![]); + on_slice(&[]); + + on_slice(&vec![1, 2]); + on_slice(&[1, 2]); + + on_slice(&vec![1, 2]); + on_slice(&[1, 2]); + #[rustfmt::skip] + on_slice(&vec!(1, 2)); + on_slice(&[1, 2]); + + on_slice(&vec![1; 2]); + on_slice(&[1; 2]); + + on_vec(&vec![]); + on_vec(&vec![1, 2]); + on_vec(&vec![1; 2]); + + // Now with non-constant expressions + let line = Line { length: 2 }; + + on_slice(&vec![2; line.length]); + on_slice(&vec![2; line.length()]); + + for a in vec![1, 2, 3] { + println!("{:?}", a); + } + + for a in vec![NonCopy, NonCopy] { + println!("{:?}", a); + } +} diff --git a/src/tools/clippy/tests/ui/vec.stderr b/src/tools/clippy/tests/ui/vec.stderr new file mode 100644 index 0000000000000..37e28ebddb553 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec.stderr @@ -0,0 +1,40 @@ +error: useless use of `vec!` + --> $DIR/vec.rs:23:14 + | +LL | on_slice(&vec![]); + | ^^^^^^^ help: you can use a slice directly: `&[]` + | + = note: `-D clippy::useless-vec` implied by `-D warnings` + +error: useless use of `vec!` + --> $DIR/vec.rs:26:14 + | +LL | on_slice(&vec![1, 2]); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:29:14 + | +LL | on_slice(&vec![1, 2]); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:32:14 + | +LL | on_slice(&vec!(1, 2)); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:35:14 + | +LL | on_slice(&vec![1; 2]); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1; 2]` + +error: useless use of `vec!` + --> $DIR/vec.rs:48:14 + | +LL | for a in vec![1, 2, 3] { + | ^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2, 3]` + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/vec_box_sized.fixed b/src/tools/clippy/tests/ui/vec_box_sized.fixed new file mode 100644 index 0000000000000..d0bee2460dd84 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_box_sized.fixed @@ -0,0 +1,38 @@ +// run-rustfix + +#![allow(dead_code)] + +struct SizedStruct(i32); +struct UnsizedStruct([i32]); +struct BigStruct([i32; 10000]); + +/// The following should trigger the lint +mod should_trigger { + use super::SizedStruct; + + struct StructWithVecBox { + sized_type: Vec, + } + + struct A(Vec); + struct B(Vec>); +} + +/// The following should not trigger the lint +mod should_not_trigger { + use super::{BigStruct, UnsizedStruct}; + + struct C(Vec>); + struct D(Vec>); + + struct StructWithVecBoxButItsUnsized { + unsized_type: Vec>, + } + + struct TraitVec { + // Regression test for #3720. This was causing an ICE. + inner: Vec>, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/vec_box_sized.rs b/src/tools/clippy/tests/ui/vec_box_sized.rs new file mode 100644 index 0000000000000..500a0ae263ea5 --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_box_sized.rs @@ -0,0 +1,38 @@ +// run-rustfix + +#![allow(dead_code)] + +struct SizedStruct(i32); +struct UnsizedStruct([i32]); +struct BigStruct([i32; 10000]); + +/// The following should trigger the lint +mod should_trigger { + use super::SizedStruct; + + struct StructWithVecBox { + sized_type: Vec>, + } + + struct A(Vec>); + struct B(Vec>>); +} + +/// The following should not trigger the lint +mod should_not_trigger { + use super::{BigStruct, UnsizedStruct}; + + struct C(Vec>); + struct D(Vec>); + + struct StructWithVecBoxButItsUnsized { + unsized_type: Vec>, + } + + struct TraitVec { + // Regression test for #3720. This was causing an ICE. + inner: Vec>, + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/vec_box_sized.stderr b/src/tools/clippy/tests/ui/vec_box_sized.stderr new file mode 100644 index 0000000000000..29bf7069e8adb --- /dev/null +++ b/src/tools/clippy/tests/ui/vec_box_sized.stderr @@ -0,0 +1,22 @@ +error: `Vec` is already on the heap, the boxing is unnecessary. + --> $DIR/vec_box_sized.rs:14:21 + | +LL | sized_type: Vec>, + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Vec` + | + = note: `-D clippy::vec-box` implied by `-D warnings` + +error: `Vec` is already on the heap, the boxing is unnecessary. + --> $DIR/vec_box_sized.rs:17:14 + | +LL | struct A(Vec>); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Vec` + +error: `Vec` is already on the heap, the boxing is unnecessary. + --> $DIR/vec_box_sized.rs:18:18 + | +LL | struct B(Vec>>); + | ^^^^^^^^^^^^^^^ help: try: `Vec` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/verbose_file_reads.rs b/src/tools/clippy/tests/ui/verbose_file_reads.rs new file mode 100644 index 0000000000000..e0065e05ade62 --- /dev/null +++ b/src/tools/clippy/tests/ui/verbose_file_reads.rs @@ -0,0 +1,28 @@ +#![warn(clippy::verbose_file_reads)] +use std::env::temp_dir; +use std::fs::File; +use std::io::Read; + +struct Struct; +// To make sure we only warn on File::{read_to_end, read_to_string} calls +impl Struct { + pub fn read_to_end(&self) {} + + pub fn read_to_string(&self) {} +} + +fn main() -> std::io::Result<()> { + let path = "foo.txt"; + // Lint shouldn't catch this + let s = Struct; + s.read_to_end(); + s.read_to_string(); + // Should catch this + let mut f = File::open(&path)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + // ...and this + let mut string_buffer = String::new(); + f.read_to_string(&mut string_buffer)?; + Ok(()) +} diff --git a/src/tools/clippy/tests/ui/verbose_file_reads.stderr b/src/tools/clippy/tests/ui/verbose_file_reads.stderr new file mode 100644 index 0000000000000..550b6ab679f19 --- /dev/null +++ b/src/tools/clippy/tests/ui/verbose_file_reads.stderr @@ -0,0 +1,19 @@ +error: use of `File::read_to_end` + --> $DIR/verbose_file_reads.rs:23:5 + | +LL | f.read_to_end(&mut buffer)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::verbose-file-reads` implied by `-D warnings` + = help: consider using `fs::read` instead + +error: use of `File::read_to_string` + --> $DIR/verbose_file_reads.rs:26:5 + | +LL | f.read_to_string(&mut string_buffer)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using `fs::read_to_string` instead + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/vtable_address_comparisons.rs b/src/tools/clippy/tests/ui/vtable_address_comparisons.rs new file mode 100644 index 0000000000000..c91d96ee18a31 --- /dev/null +++ b/src/tools/clippy/tests/ui/vtable_address_comparisons.rs @@ -0,0 +1,42 @@ +use std::fmt::Debug; +use std::ptr; +use std::rc::Rc; +use std::sync::Arc; + +#[warn(clippy::vtable_address_comparisons)] +fn main() { + let a: *const dyn Debug = &1 as &dyn Debug; + let b: *const dyn Debug = &1 as &dyn Debug; + + // These should fail: + let _ = a == b; + let _ = a != b; + let _ = a < b; + let _ = a <= b; + let _ = a > b; + let _ = a >= b; + ptr::eq(a, b); + + let a = &1 as &dyn Debug; + let b = &1 as &dyn Debug; + ptr::eq(a, b); + + let a: Rc = Rc::new(1); + Rc::ptr_eq(&a, &a); + + let a: Arc = Arc::new(1); + Arc::ptr_eq(&a, &a); + + // These should be fine: + let a = &1; + ptr::eq(a, a); + + let a = Rc::new(1); + Rc::ptr_eq(&a, &a); + + let a = Arc::new(1); + Arc::ptr_eq(&a, &a); + + let a: &[u8] = b""; + ptr::eq(a, a); +} diff --git a/src/tools/clippy/tests/ui/vtable_address_comparisons.stderr b/src/tools/clippy/tests/ui/vtable_address_comparisons.stderr new file mode 100644 index 0000000000000..76bd57217d784 --- /dev/null +++ b/src/tools/clippy/tests/ui/vtable_address_comparisons.stderr @@ -0,0 +1,83 @@ +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:12:13 + | +LL | let _ = a == b; + | ^^^^^^ + | + = note: `-D clippy::vtable-address-comparisons` implied by `-D warnings` + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:13:13 + | +LL | let _ = a != b; + | ^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:14:13 + | +LL | let _ = a < b; + | ^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:15:13 + | +LL | let _ = a <= b; + | ^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:16:13 + | +LL | let _ = a > b; + | ^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:17:13 + | +LL | let _ = a >= b; + | ^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:18:5 + | +LL | ptr::eq(a, b); + | ^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:22:5 + | +LL | ptr::eq(a, b); + | ^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:25:5 + | +LL | Rc::ptr_eq(&a, &a); + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: comparing trait object pointers compares a non-unique vtable address + --> $DIR/vtable_address_comparisons.rs:28:5 + | +LL | Arc::ptr_eq(&a, &a); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider extracting and comparing data pointers only + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/while_let_loop.rs b/src/tools/clippy/tests/ui/while_let_loop.rs new file mode 100644 index 0000000000000..3ce699f551b20 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_loop.rs @@ -0,0 +1,119 @@ +#![warn(clippy::while_let_loop)] + +fn main() { + let y = Some(true); + loop { + if let Some(_x) = y { + let _v = 1; + } else { + break; + } + } + + #[allow(clippy::never_loop)] + loop { + // no error, break is not in else clause + if let Some(_x) = y { + let _v = 1; + } + break; + } + + loop { + match y { + Some(_x) => true, + None => break, + }; + } + + loop { + let x = match y { + Some(x) => x, + None => break, + }; + let _x = x; + let _str = "foo"; + } + + loop { + let x = match y { + Some(x) => x, + None => break, + }; + { + let _a = "bar"; + }; + { + let _b = "foobar"; + } + } + + loop { + // no error, else branch does something other than break + match y { + Some(_x) => true, + _ => { + let _z = 1; + break; + }, + }; + } + + while let Some(x) = y { + // no error, obviously + println!("{}", x); + } + + // #675, this used to have a wrong suggestion + loop { + let (e, l) = match "".split_whitespace().next() { + Some(word) => (word.is_empty(), word.len()), + None => break, + }; + + let _ = (e, l); + } +} + +fn issue771() { + let mut a = 100; + let b = Some(true); + loop { + if a > 10 { + break; + } + + match b { + Some(_) => a = 0, + None => break, + } + } +} + +fn issue1017() { + let r: Result = Ok(42); + let mut len = 1337; + + loop { + match r { + Err(_) => len = 0, + Ok(length) => { + len = length; + break; + }, + } + } +} + +#[allow(clippy::never_loop)] +fn issue1948() { + // should not trigger clippy::while_let_loop lint because break passes an expression + let a = Some(10); + let b = loop { + if let Some(c) = a { + break Some(c); + } else { + break None; + } + }; +} diff --git a/src/tools/clippy/tests/ui/while_let_loop.stderr b/src/tools/clippy/tests/ui/while_let_loop.stderr new file mode 100644 index 0000000000000..13dd0ee224c10 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_loop.stderr @@ -0,0 +1,63 @@ +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:5:5 + | +LL | / loop { +LL | | if let Some(_x) = y { +LL | | let _v = 1; +LL | | } else { +LL | | break; +LL | | } +LL | | } + | |_____^ help: try: `while let Some(_x) = y { .. }` + | + = note: `-D clippy::while-let-loop` implied by `-D warnings` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:22:5 + | +LL | / loop { +LL | | match y { +LL | | Some(_x) => true, +LL | | None => break, +LL | | }; +LL | | } + | |_____^ help: try: `while let Some(_x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:29:5 + | +LL | / loop { +LL | | let x = match y { +LL | | Some(x) => x, +LL | | None => break, +... | +LL | | let _str = "foo"; +LL | | } + | |_____^ help: try: `while let Some(x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:38:5 + | +LL | / loop { +LL | | let x = match y { +LL | | Some(x) => x, +LL | | None => break, +... | +LL | | } +LL | | } + | |_____^ help: try: `while let Some(x) = y { .. }` + +error: this loop could be written as a `while let` loop + --> $DIR/while_let_loop.rs:68:5 + | +LL | / loop { +LL | | let (e, l) = match "".split_whitespace().next() { +LL | | Some(word) => (word.is_empty(), word.len()), +LL | | None => break, +... | +LL | | let _ = (e, l); +LL | | } + | |_____^ help: try: `while let Some(word) = "".split_whitespace().next() { .. }` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.fixed b/src/tools/clippy/tests/ui/while_let_on_iterator.fixed new file mode 100644 index 0000000000000..f5fcabf63fd39 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.fixed @@ -0,0 +1,160 @@ +// run-rustfix + +#![warn(clippy::while_let_on_iterator)] +#![allow(clippy::never_loop, unreachable_code, unused_mut)] + +fn base() { + let mut iter = 1..20; + for x in iter { + println!("{}", x); + } + + let mut iter = 1..20; + for x in iter { + println!("{}", x); + } + + let mut iter = 1..20; + for _ in iter {} + + let mut iter = 1..20; + while let None = iter.next() {} // this is fine (if nonsensical) + + let mut iter = 1..20; + if let Some(x) = iter.next() { + // also fine + println!("{}", x) + } + + // the following shouldn't warn because it can't be written with a for loop + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()) + } + + // neither can this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()); + } + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + break; + } + println!("Remaining iter {:?}", iter); + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + iter = 1..20; + } +} + +// Issue #1188 +fn refutable() { + let a = [42, 1337]; + let mut b = a.iter(); + + // consume all the 42s + while let Some(&42) = b.next() {} + + let a = [(1, 2, 3)]; + let mut b = a.iter(); + + while let Some(&(1, 2, 3)) = b.next() {} + + let a = [Some(42)]; + let mut b = a.iter(); + + while let Some(&None) = b.next() {} + + /* This gives “refutable pattern in `for` loop binding: `&_` not covered” + for &42 in b {} + for &(1, 2, 3) in b {} + for &Option::None in b.next() {} + // */ +} + +fn nested_loops() { + let a = [42, 1337]; + let mut y = a.iter(); + loop { + // x is reused, so don't lint here + while let Some(_) = y.next() {} + } + + let mut y = a.iter(); + for _ in 0..2 { + while let Some(_) = y.next() { + // y is reused, don't lint + } + } + + loop { + let mut y = a.iter(); + for _ in y { + // use a for loop here + } + } +} + +fn issue1121() { + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(&value) = values.iter().next() { + values.remove(&value); + } +} + +fn issue2965() { + // This should not cause an ICE and suggest: + // + // for _ in values.iter() {} + // + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() {} +} + +fn issue3670() { + let array = [Some(0), None, Some(1)]; + let mut iter = array.iter(); + + while let Some(elem) = iter.next() { + let _ = elem.or_else(|| *iter.next()?); + } +} + +fn issue1654() { + // should not lint if the iterator is generated on every iteration + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() { + values.remove(&1); + } + + while let Some(..) = values.iter().map(|x| x + 1).next() {} + + let chars = "Hello, World!".char_indices(); + while let Some((i, ch)) = chars.clone().next() { + println!("{}: {}", i, ch); + } +} + +fn main() { + base(); + refutable(); + nested_loops(); + issue1121(); + issue2965(); + issue3670(); + issue1654(); +} diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.rs b/src/tools/clippy/tests/ui/while_let_on_iterator.rs new file mode 100644 index 0000000000000..04dce8a028984 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.rs @@ -0,0 +1,160 @@ +// run-rustfix + +#![warn(clippy::while_let_on_iterator)] +#![allow(clippy::never_loop, unreachable_code, unused_mut)] + +fn base() { + let mut iter = 1..20; + while let Option::Some(x) = iter.next() { + println!("{}", x); + } + + let mut iter = 1..20; + while let Some(x) = iter.next() { + println!("{}", x); + } + + let mut iter = 1..20; + while let Some(_) = iter.next() {} + + let mut iter = 1..20; + while let None = iter.next() {} // this is fine (if nonsensical) + + let mut iter = 1..20; + if let Some(x) = iter.next() { + // also fine + println!("{}", x) + } + + // the following shouldn't warn because it can't be written with a for loop + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()) + } + + // neither can this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + println!("next: {:?}", iter.next()); + } + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + break; + } + println!("Remaining iter {:?}", iter); + + // or this + let mut iter = 1u32..20; + while let Some(_) = iter.next() { + iter = 1..20; + } +} + +// Issue #1188 +fn refutable() { + let a = [42, 1337]; + let mut b = a.iter(); + + // consume all the 42s + while let Some(&42) = b.next() {} + + let a = [(1, 2, 3)]; + let mut b = a.iter(); + + while let Some(&(1, 2, 3)) = b.next() {} + + let a = [Some(42)]; + let mut b = a.iter(); + + while let Some(&None) = b.next() {} + + /* This gives “refutable pattern in `for` loop binding: `&_` not covered” + for &42 in b {} + for &(1, 2, 3) in b {} + for &Option::None in b.next() {} + // */ +} + +fn nested_loops() { + let a = [42, 1337]; + let mut y = a.iter(); + loop { + // x is reused, so don't lint here + while let Some(_) = y.next() {} + } + + let mut y = a.iter(); + for _ in 0..2 { + while let Some(_) = y.next() { + // y is reused, don't lint + } + } + + loop { + let mut y = a.iter(); + while let Some(_) = y.next() { + // use a for loop here + } + } +} + +fn issue1121() { + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(&value) = values.iter().next() { + values.remove(&value); + } +} + +fn issue2965() { + // This should not cause an ICE and suggest: + // + // for _ in values.iter() {} + // + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() {} +} + +fn issue3670() { + let array = [Some(0), None, Some(1)]; + let mut iter = array.iter(); + + while let Some(elem) = iter.next() { + let _ = elem.or_else(|| *iter.next()?); + } +} + +fn issue1654() { + // should not lint if the iterator is generated on every iteration + use std::collections::HashSet; + let mut values = HashSet::new(); + values.insert(1); + + while let Some(..) = values.iter().next() { + values.remove(&1); + } + + while let Some(..) = values.iter().map(|x| x + 1).next() {} + + let chars = "Hello, World!".char_indices(); + while let Some((i, ch)) = chars.clone().next() { + println!("{}: {}", i, ch); + } +} + +fn main() { + base(); + refutable(); + nested_loops(); + issue1121(); + issue2965(); + issue3670(); + issue1654(); +} diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.stderr b/src/tools/clippy/tests/ui/while_let_on_iterator.stderr new file mode 100644 index 0000000000000..6de138d7227b2 --- /dev/null +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.stderr @@ -0,0 +1,28 @@ +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:8:5 + | +LL | while let Option::Some(x) = iter.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` + | + = note: `-D clippy::while-let-on-iterator` implied by `-D warnings` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:13:5 + | +LL | while let Some(x) = iter.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:18:5 + | +LL | while let Some(_) = iter.next() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:97:9 + | +LL | while let Some(_) = y.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/wild_in_or_pats.rs b/src/tools/clippy/tests/ui/wild_in_or_pats.rs new file mode 100644 index 0000000000000..ad600f125772f --- /dev/null +++ b/src/tools/clippy/tests/ui/wild_in_or_pats.rs @@ -0,0 +1,36 @@ +#![warn(clippy::wildcard_in_or_patterns)] + +fn main() { + match "foo" { + "a" => { + dbg!("matched a"); + }, + "bar" | _ => { + dbg!("matched (bar or) wild"); + }, + }; + match "foo" { + "a" => { + dbg!("matched a"); + }, + "bar" | "bar2" | _ => { + dbg!("matched (bar or bar2 or) wild"); + }, + }; + match "foo" { + "a" => { + dbg!("matched a"); + }, + _ | "bar" | _ => { + dbg!("matched (bar or) wild"); + }, + }; + match "foo" { + "a" => { + dbg!("matched a"); + }, + _ | "bar" => { + dbg!("matched (bar or) wild"); + }, + }; +} diff --git a/src/tools/clippy/tests/ui/wild_in_or_pats.stderr b/src/tools/clippy/tests/ui/wild_in_or_pats.stderr new file mode 100644 index 0000000000000..33c34cbbd4088 --- /dev/null +++ b/src/tools/clippy/tests/ui/wild_in_or_pats.stderr @@ -0,0 +1,35 @@ +error: wildcard pattern covers any other pattern as it will match anyway. + --> $DIR/wild_in_or_pats.rs:8:9 + | +LL | "bar" | _ => { + | ^^^^^^^^^ + | + = note: `-D clippy::wildcard-in-or-patterns` implied by `-D warnings` + = help: Consider handling `_` separately. + +error: wildcard pattern covers any other pattern as it will match anyway. + --> $DIR/wild_in_or_pats.rs:16:9 + | +LL | "bar" | "bar2" | _ => { + | ^^^^^^^^^^^^^^^^^^ + | + = help: Consider handling `_` separately. + +error: wildcard pattern covers any other pattern as it will match anyway. + --> $DIR/wild_in_or_pats.rs:24:9 + | +LL | _ | "bar" | _ => { + | ^^^^^^^^^^^^^ + | + = help: Consider handling `_` separately. + +error: wildcard pattern covers any other pattern as it will match anyway. + --> $DIR/wild_in_or_pats.rs:32:9 + | +LL | _ | "bar" => { + | ^^^^^^^^^ + | + = help: Consider handling `_` separately. + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed new file mode 100644 index 0000000000000..2aa24ea1156aa --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed @@ -0,0 +1,101 @@ +// run-rustfix + +#![deny(clippy::wildcard_enum_match_arm)] +#![allow( + unreachable_code, + unused_variables, + dead_code, + clippy::single_match, + clippy::wildcard_in_or_patterns +)] + +use std::io::ErrorKind; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), + Cyan, +} + +impl Color { + fn is_monochrome(self) -> bool { + match self { + Color::Red | Color::Green | Color::Blue => true, + Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0, + Color::Cyan => false, + } + } +} + +fn main() { + let color = Color::Rgb(0, 0, 127); + match color { + Color::Red => println!("Red"), + Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan => eprintln!("Not red"), + }; + match color { + Color::Red => println!("Red"), + _not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan => eprintln!("Not red"), + }; + let _str = match color { + Color::Red => "Red".to_owned(), + not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan => format!("{:?}", not_red), + }; + match color { + Color::Red => {}, + Color::Green => {}, + Color::Blue => {}, + Color::Cyan => {}, + c if c.is_monochrome() => {}, + Color::Rgb(_, _, _) => {}, + }; + let _str = match color { + Color::Red => "Red", + c @ Color::Green | c @ Color::Blue | c @ Color::Rgb(_, _, _) | c @ Color::Cyan => "Not red", + }; + match color { + Color::Rgb(r, _, _) if r > 0 => "Some red", + Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan => "No red", + }; + match color { + Color::Red | Color::Green | Color::Blue | Color::Cyan => {}, + Color::Rgb(..) => {}, + }; + let x: u8 = unimplemented!(); + match x { + 0 => {}, + 140 => {}, + _ => {}, + }; + // We need to use an enum not defined in this test because non_exhaustive is ignored for the + // purposes of dead code analysis within a crate. + let error_kind = ErrorKind::NotFound; + match error_kind { + ErrorKind::NotFound => {}, + std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::NotConnected | std::io::ErrorKind::AddrInUse | std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidData | std::io::ErrorKind::TimedOut | std::io::ErrorKind::WriteZero | std::io::ErrorKind::Interrupted | std::io::ErrorKind::Other | std::io::ErrorKind::UnexpectedEof | _ => {}, + } + match error_kind { + ErrorKind::NotFound => {}, + ErrorKind::PermissionDenied => {}, + ErrorKind::ConnectionRefused => {}, + ErrorKind::ConnectionReset => {}, + ErrorKind::ConnectionAborted => {}, + ErrorKind::NotConnected => {}, + ErrorKind::AddrInUse => {}, + ErrorKind::AddrNotAvailable => {}, + ErrorKind::BrokenPipe => {}, + ErrorKind::AlreadyExists => {}, + ErrorKind::WouldBlock => {}, + ErrorKind::InvalidInput => {}, + ErrorKind::InvalidData => {}, + ErrorKind::TimedOut => {}, + ErrorKind::WriteZero => {}, + ErrorKind::Interrupted => {}, + ErrorKind::Other => {}, + ErrorKind::UnexpectedEof => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.rs b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.rs new file mode 100644 index 0000000000000..07c93feaf284e --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.rs @@ -0,0 +1,101 @@ +// run-rustfix + +#![deny(clippy::wildcard_enum_match_arm)] +#![allow( + unreachable_code, + unused_variables, + dead_code, + clippy::single_match, + clippy::wildcard_in_or_patterns +)] + +use std::io::ErrorKind; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), + Cyan, +} + +impl Color { + fn is_monochrome(self) -> bool { + match self { + Color::Red | Color::Green | Color::Blue => true, + Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0, + Color::Cyan => false, + } + } +} + +fn main() { + let color = Color::Rgb(0, 0, 127); + match color { + Color::Red => println!("Red"), + _ => eprintln!("Not red"), + }; + match color { + Color::Red => println!("Red"), + _not_red => eprintln!("Not red"), + }; + let _str = match color { + Color::Red => "Red".to_owned(), + not_red => format!("{:?}", not_red), + }; + match color { + Color::Red => {}, + Color::Green => {}, + Color::Blue => {}, + Color::Cyan => {}, + c if c.is_monochrome() => {}, + Color::Rgb(_, _, _) => {}, + }; + let _str = match color { + Color::Red => "Red", + c @ Color::Green | c @ Color::Blue | c @ Color::Rgb(_, _, _) | c @ Color::Cyan => "Not red", + }; + match color { + Color::Rgb(r, _, _) if r > 0 => "Some red", + _ => "No red", + }; + match color { + Color::Red | Color::Green | Color::Blue | Color::Cyan => {}, + Color::Rgb(..) => {}, + }; + let x: u8 = unimplemented!(); + match x { + 0 => {}, + 140 => {}, + _ => {}, + }; + // We need to use an enum not defined in this test because non_exhaustive is ignored for the + // purposes of dead code analysis within a crate. + let error_kind = ErrorKind::NotFound; + match error_kind { + ErrorKind::NotFound => {}, + _ => {}, + } + match error_kind { + ErrorKind::NotFound => {}, + ErrorKind::PermissionDenied => {}, + ErrorKind::ConnectionRefused => {}, + ErrorKind::ConnectionReset => {}, + ErrorKind::ConnectionAborted => {}, + ErrorKind::NotConnected => {}, + ErrorKind::AddrInUse => {}, + ErrorKind::AddrNotAvailable => {}, + ErrorKind::BrokenPipe => {}, + ErrorKind::AlreadyExists => {}, + ErrorKind::WouldBlock => {}, + ErrorKind::InvalidInput => {}, + ErrorKind::InvalidData => {}, + ErrorKind::TimedOut => {}, + ErrorKind::WriteZero => {}, + ErrorKind::Interrupted => {}, + ErrorKind::Other => {}, + ErrorKind::UnexpectedEof => {}, + _ => {}, + } +} diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr new file mode 100644 index 0000000000000..7151a5c5770bf --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr @@ -0,0 +1,38 @@ +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:37:9 + | +LL | _ => eprintln!("Not red"), + | ^ help: try this: `Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan` + | +note: the lint level is defined here + --> $DIR/wildcard_enum_match_arm.rs:3:9 + | +LL | #![deny(clippy::wildcard_enum_match_arm)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:41:9 + | +LL | _not_red => eprintln!("Not red"), + | ^^^^^^^^ help: try this: `_not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan` + +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:45:9 + | +LL | not_red => format!("{:?}", not_red), + | ^^^^^^^ help: try this: `not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan` + +error: wildcard match will miss any future added variants + --> $DIR/wildcard_enum_match_arm.rs:61:9 + | +LL | _ => "No red", + | ^ help: try this: `Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan` + +error: match on non-exhaustive enum doesn't explicitly match all known variants + --> $DIR/wildcard_enum_match_arm.rs:78:9 + | +LL | _ => {}, + | ^ help: try this: `std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::ConnectionRefused | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::NotConnected | std::io::ErrorKind::AddrInUse | std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::AlreadyExists | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::InvalidInput | std::io::ErrorKind::InvalidData | std::io::ErrorKind::TimedOut | std::io::ErrorKind::WriteZero | std::io::ErrorKind::Interrupted | std::io::ErrorKind::Other | std::io::ErrorKind::UnexpectedEof | _` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/wildcard_imports.fixed b/src/tools/clippy/tests/ui/wildcard_imports.fixed new file mode 100644 index 0000000000000..ed6cc00ef0480 --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_imports.fixed @@ -0,0 +1,157 @@ +// run-rustfix +// aux-build:wildcard_imports_helper.rs + +#![warn(clippy::wildcard_imports)] +//#![allow(clippy::redundant_pub_crate)] +#![allow(unused)] +#![warn(unused_imports)] + +extern crate wildcard_imports_helper; + +use crate::fn_mod::foo; +use crate::mod_mod::inner_mod; +use crate::multi_fn_mod::{multi_bar, multi_foo, multi_inner_mod}; +#[macro_use] +use crate::struct_mod::{A, inner_struct_mod}; + +#[allow(unused_imports)] +use wildcard_imports_helper::inner::inner_for_self_import; +use wildcard_imports_helper::inner::inner_for_self_import::inner_extern_bar; +use wildcard_imports_helper::{ExternA, extern_foo}; + +use std::io::prelude::*; + +struct ReadFoo; + +impl Read for ReadFoo { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Ok(0) + } +} + +mod fn_mod { + pub fn foo() {} +} + +mod mod_mod { + pub mod inner_mod { + pub fn foo() {} + } +} + +mod multi_fn_mod { + pub fn multi_foo() {} + pub fn multi_bar() {} + pub fn multi_baz() {} + pub mod multi_inner_mod { + pub fn foo() {} + } +} + +mod struct_mod { + pub struct A; + pub struct B; + pub mod inner_struct_mod { + pub struct C; + } + + #[macro_export] + macro_rules! double_struct_import_test { + () => { + let _ = A; + }; + } +} + +fn main() { + foo(); + multi_foo(); + multi_bar(); + multi_inner_mod::foo(); + inner_mod::foo(); + extern_foo(); + inner_extern_bar(); + + let _ = A; + let _ = inner_struct_mod::C; + let _ = ExternA; + + double_struct_import_test!(); + double_struct_import_test!(); +} + +mod in_fn_test { + pub use self::inner_exported::*; + #[allow(unused_imports)] + pub(crate) use self::inner_exported2::*; + + fn test_intern() { + use crate::fn_mod::foo; + + foo(); + } + + fn test_extern() { + use wildcard_imports_helper::inner::inner_for_self_import::{self, inner_extern_foo}; + use wildcard_imports_helper::{ExternA, extern_foo}; + + inner_for_self_import::inner_extern_foo(); + inner_extern_foo(); + + extern_foo(); + + let _ = ExternA; + } + + fn test_inner_nested() { + use self::{inner::inner_foo, inner2::inner_bar}; + + inner_foo(); + inner_bar(); + } + + fn test_extern_reexported() { + use wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}; + + extern_exported(); + let _ = ExternExportedStruct; + let _ = ExternExportedEnum::A; + } + + mod inner_exported { + pub fn exported() {} + pub struct ExportedStruct; + pub enum ExportedEnum { + A, + } + } + + mod inner_exported2 { + pub(crate) fn exported2() {} + } + + mod inner { + pub fn inner_foo() {} + } + + mod inner2 { + pub fn inner_bar() {} + } +} + +fn test_reexported() { + use crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}; + + exported(); + let _ = ExportedStruct; + let _ = ExportedEnum::A; +} + +#[rustfmt::skip] +fn test_weird_formatting() { + use crate:: in_fn_test::exported; + use crate:: fn_mod::foo; + + exported(); + foo(); +} diff --git a/src/tools/clippy/tests/ui/wildcard_imports.rs b/src/tools/clippy/tests/ui/wildcard_imports.rs new file mode 100644 index 0000000000000..c6d6efaece817 --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_imports.rs @@ -0,0 +1,158 @@ +// run-rustfix +// aux-build:wildcard_imports_helper.rs + +#![warn(clippy::wildcard_imports)] +//#![allow(clippy::redundant_pub_crate)] +#![allow(unused)] +#![warn(unused_imports)] + +extern crate wildcard_imports_helper; + +use crate::fn_mod::*; +use crate::mod_mod::*; +use crate::multi_fn_mod::*; +#[macro_use] +use crate::struct_mod::*; + +#[allow(unused_imports)] +use wildcard_imports_helper::inner::inner_for_self_import; +use wildcard_imports_helper::inner::inner_for_self_import::*; +use wildcard_imports_helper::*; + +use std::io::prelude::*; + +struct ReadFoo; + +impl Read for ReadFoo { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Ok(0) + } +} + +mod fn_mod { + pub fn foo() {} +} + +mod mod_mod { + pub mod inner_mod { + pub fn foo() {} + } +} + +mod multi_fn_mod { + pub fn multi_foo() {} + pub fn multi_bar() {} + pub fn multi_baz() {} + pub mod multi_inner_mod { + pub fn foo() {} + } +} + +mod struct_mod { + pub struct A; + pub struct B; + pub mod inner_struct_mod { + pub struct C; + } + + #[macro_export] + macro_rules! double_struct_import_test { + () => { + let _ = A; + }; + } +} + +fn main() { + foo(); + multi_foo(); + multi_bar(); + multi_inner_mod::foo(); + inner_mod::foo(); + extern_foo(); + inner_extern_bar(); + + let _ = A; + let _ = inner_struct_mod::C; + let _ = ExternA; + + double_struct_import_test!(); + double_struct_import_test!(); +} + +mod in_fn_test { + pub use self::inner_exported::*; + #[allow(unused_imports)] + pub(crate) use self::inner_exported2::*; + + fn test_intern() { + use crate::fn_mod::*; + + foo(); + } + + fn test_extern() { + use wildcard_imports_helper::inner::inner_for_self_import::{self, *}; + use wildcard_imports_helper::*; + + inner_for_self_import::inner_extern_foo(); + inner_extern_foo(); + + extern_foo(); + + let _ = ExternA; + } + + fn test_inner_nested() { + use self::{inner::*, inner2::*}; + + inner_foo(); + inner_bar(); + } + + fn test_extern_reexported() { + use wildcard_imports_helper::*; + + extern_exported(); + let _ = ExternExportedStruct; + let _ = ExternExportedEnum::A; + } + + mod inner_exported { + pub fn exported() {} + pub struct ExportedStruct; + pub enum ExportedEnum { + A, + } + } + + mod inner_exported2 { + pub(crate) fn exported2() {} + } + + mod inner { + pub fn inner_foo() {} + } + + mod inner2 { + pub fn inner_bar() {} + } +} + +fn test_reexported() { + use crate::in_fn_test::*; + + exported(); + let _ = ExportedStruct; + let _ = ExportedEnum::A; +} + +#[rustfmt::skip] +fn test_weird_formatting() { + use crate:: in_fn_test:: * ; + use crate:: fn_mod:: + *; + + exported(); + foo(); +} diff --git a/src/tools/clippy/tests/ui/wildcard_imports.stderr b/src/tools/clippy/tests/ui/wildcard_imports.stderr new file mode 100644 index 0000000000000..050e4c6304f05 --- /dev/null +++ b/src/tools/clippy/tests/ui/wildcard_imports.stderr @@ -0,0 +1,96 @@ +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:11:5 + | +LL | use crate::fn_mod::*; + | ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo` + | + = note: `-D clippy::wildcard-imports` implied by `-D warnings` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:12:5 + | +LL | use crate::mod_mod::*; + | ^^^^^^^^^^^^^^^^^ help: try: `crate::mod_mod::inner_mod` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:13:5 + | +LL | use crate::multi_fn_mod::*; + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::multi_fn_mod::{multi_bar, multi_foo, multi_inner_mod}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:15:5 + | +LL | use crate::struct_mod::*; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::struct_mod::{A, inner_struct_mod}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:19:5 + | +LL | use wildcard_imports_helper::inner::inner_for_self_import::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::inner::inner_for_self_import::inner_extern_bar` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:20:5 + | +LL | use wildcard_imports_helper::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:89:13 + | +LL | use crate::fn_mod::*; + | ^^^^^^^^^^^^^^^^ help: try: `crate::fn_mod::foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:95:75 + | +LL | use wildcard_imports_helper::inner::inner_for_self_import::{self, *}; + | ^ help: try: `inner_extern_foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:96:13 + | +LL | use wildcard_imports_helper::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternA, extern_foo}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:107:20 + | +LL | use self::{inner::*, inner2::*}; + | ^^^^^^^^ help: try: `inner::inner_foo` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:107:30 + | +LL | use self::{inner::*, inner2::*}; + | ^^^^^^^^^ help: try: `inner2::inner_bar` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:114:13 + | +LL | use wildcard_imports_helper::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `wildcard_imports_helper::{ExternExportedEnum, ExternExportedStruct, extern_exported}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:143:9 + | +LL | use crate::in_fn_test::*; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `crate::in_fn_test::{ExportedEnum, ExportedStruct, exported}` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:152:9 + | +LL | use crate:: in_fn_test:: * ; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate:: in_fn_test::exported` + +error: usage of wildcard import + --> $DIR/wildcard_imports.rs:153:9 + | +LL | use crate:: fn_mod:: + | _________^ +LL | | *; + | |_________^ help: try: `crate:: fn_mod::foo` + +error: aborting due to 15 previous errors + diff --git a/src/tools/clippy/tests/ui/write_literal.rs b/src/tools/clippy/tests/ui/write_literal.rs new file mode 100644 index 0000000000000..d8205c5eb6700 --- /dev/null +++ b/src/tools/clippy/tests/ui/write_literal.rs @@ -0,0 +1,43 @@ +#![allow(unused_must_use)] +#![warn(clippy::write_literal)] + +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // these should be fine + write!(&mut v, "Hello"); + writeln!(&mut v, "Hello"); + let world = "world"; + writeln!(&mut v, "Hello {}", world); + writeln!(&mut v, "Hello {world}", world = world); + writeln!(&mut v, "3 in hex is {:X}", 3); + writeln!(&mut v, "2 + 1 = {:.4}", 3); + writeln!(&mut v, "2 + 1 = {:5.4}", 3); + writeln!(&mut v, "Debug test {:?}", "hello, world"); + writeln!(&mut v, "{0:8} {1:>8}", "hello", "world"); + writeln!(&mut v, "{1:8} {0:>8}", "hello", "world"); + writeln!(&mut v, "{foo:8} {bar:>8}", foo = "hello", bar = "world"); + writeln!(&mut v, "{bar:8} {foo:>8}", foo = "hello", bar = "world"); + writeln!(&mut v, "{number:>width$}", number = 1, width = 6); + writeln!(&mut v, "{number:>0width$}", number = 1, width = 6); + + // these should throw warnings + writeln!(&mut v, "{} of {:b} people know binary, the other half doesn't", 1, 2); + write!(&mut v, "Hello {}", "world"); + writeln!(&mut v, "Hello {} {}", world, "world"); + writeln!(&mut v, "Hello {}", "world"); + writeln!(&mut v, "10 / 4 is {}", 2.5); + writeln!(&mut v, "2 + 1 = {}", 3); + + // positional args don't change the fact + // that we're using a literal -- this should + // throw a warning + writeln!(&mut v, "{0} {1}", "hello", "world"); + writeln!(&mut v, "{1} {0}", "hello", "world"); + + // named args shouldn't change anything either + writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); + writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); +} diff --git a/src/tools/clippy/tests/ui/write_literal.stderr b/src/tools/clippy/tests/ui/write_literal.stderr new file mode 100644 index 0000000000000..54a787fe555af --- /dev/null +++ b/src/tools/clippy/tests/ui/write_literal.stderr @@ -0,0 +1,88 @@ +error: literal with an empty format string + --> $DIR/write_literal.rs:27:79 + | +LL | writeln!(&mut v, "{} of {:b} people know binary, the other half doesn't", 1, 2); + | ^ + | + = note: `-D clippy::write-literal` implied by `-D warnings` + +error: literal with an empty format string + --> $DIR/write_literal.rs:28:32 + | +LL | write!(&mut v, "Hello {}", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:29:44 + | +LL | writeln!(&mut v, "Hello {} {}", world, "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:30:34 + | +LL | writeln!(&mut v, "Hello {}", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:31:38 + | +LL | writeln!(&mut v, "10 / 4 is {}", 2.5); + | ^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:32:36 + | +LL | writeln!(&mut v, "2 + 1 = {}", 3); + | ^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:37:33 + | +LL | writeln!(&mut v, "{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:37:42 + | +LL | writeln!(&mut v, "{0} {1}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:38:33 + | +LL | writeln!(&mut v, "{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:38:42 + | +LL | writeln!(&mut v, "{1} {0}", "hello", "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:41:43 + | +LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:41:58 + | +LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:42:43 + | +LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: literal with an empty format string + --> $DIR/write_literal.rs:42:58 + | +LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^ + +error: aborting due to 14 previous errors + diff --git a/src/tools/clippy/tests/ui/write_with_newline.rs b/src/tools/clippy/tests/ui/write_with_newline.rs new file mode 100644 index 0000000000000..93afd73d1114d --- /dev/null +++ b/src/tools/clippy/tests/ui/write_with_newline.rs @@ -0,0 +1,58 @@ +// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934 +// // run-rustfix + +#![allow(clippy::write_literal)] +#![warn(clippy::write_with_newline)] + +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // These should fail + write!(&mut v, "Hello\n"); + write!(&mut v, "Hello {}\n", "world"); + write!(&mut v, "Hello {} {}\n", "world", "#2"); + write!(&mut v, "{}\n", 1265); + + // These should be fine + write!(&mut v, ""); + write!(&mut v, "Hello"); + writeln!(&mut v, "Hello"); + writeln!(&mut v, "Hello\n"); + writeln!(&mut v, "Hello {}\n", "world"); + write!(&mut v, "Issue\n{}", 1265); + write!(&mut v, "{}", 1265); + write!(&mut v, "\n{}", 1275); + write!(&mut v, "\n\n"); + write!(&mut v, "like eof\n\n"); + write!(&mut v, "Hello {} {}\n\n", "world", "#2"); + writeln!(&mut v, "\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 + writeln!(&mut v, "\nbla\n\n"); // #3126 + + // Escaping + write!(&mut v, "\\n"); // #3514 + write!(&mut v, "\\\n"); // should fail + write!(&mut v, "\\\\n"); + + // Raw strings + write!(&mut v, r"\n"); // #3778 + + // Literal newlines should also fail + write!( + &mut v, + " +" + ); + write!( + &mut v, + r" +" + ); + + // Don't warn on CRLF (#4208) + write!(&mut v, "\r\n"); + write!(&mut v, "foo\r\n"); + write!(&mut v, "\\r\n"); //~ ERROR + write!(&mut v, "foo\rbar\n"); +} diff --git a/src/tools/clippy/tests/ui/write_with_newline.stderr b/src/tools/clippy/tests/ui/write_with_newline.stderr new file mode 100644 index 0000000000000..2473329ca7276 --- /dev/null +++ b/src/tools/clippy/tests/ui/write_with_newline.stderr @@ -0,0 +1,114 @@ +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:13:5 + | +LL | write!(&mut v, "Hello/n"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::write-with-newline` implied by `-D warnings` +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "Hello"); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:14:5 + | +LL | write!(&mut v, "Hello {}/n", "world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "Hello {}", "world"); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:15:5 + | +LL | write!(&mut v, "Hello {} {}/n", "world", "#2"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "Hello {} {}", "world", "#2"); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:16:5 + | +LL | write!(&mut v, "{}/n", 1265); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "{}", 1265); + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:35:5 + | +LL | write!(&mut v, "//n"); // should fail + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "/"); // should fail + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:42:5 + | +LL | / write!( +LL | | &mut v, +LL | | " +LL | | " +LL | | ); + | |_____^ + | +help: use `writeln!()` instead + | +LL | writeln!( +LL | &mut v, +LL | "" + | + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:47:5 + | +LL | / write!( +LL | | &mut v, +LL | | r" +LL | | " +LL | | ); + | |_____^ + | +help: use `writeln!()` instead + | +LL | writeln!( +LL | &mut v, +LL | r"" + | + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:56:5 + | +LL | write!(&mut v, "/r/n"); //~ ERROR + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "/r"); //~ ERROR + | ^^^^^^^ -- + +error: using `write!()` with a format string that ends in a single newline + --> $DIR/write_with_newline.rs:57:5 + | +LL | write!(&mut v, "foo/rbar/n"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `writeln!()` instead + | +LL | writeln!(&mut v, "foo/rbar"); + | ^^^^^^^ -- + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/writeln_empty_string.fixed b/src/tools/clippy/tests/ui/writeln_empty_string.fixed new file mode 100644 index 0000000000000..c3ac15b03751c --- /dev/null +++ b/src/tools/clippy/tests/ui/writeln_empty_string.fixed @@ -0,0 +1,20 @@ +// run-rustfix + +#![allow(unused_must_use)] +#![warn(clippy::writeln_empty_string)] +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // These should fail + writeln!(&mut v); + + let mut suggestion = Vec::new(); + writeln!(&mut suggestion); + + // These should be fine + writeln!(&mut v); + writeln!(&mut v, " "); + write!(&mut v, ""); +} diff --git a/src/tools/clippy/tests/ui/writeln_empty_string.rs b/src/tools/clippy/tests/ui/writeln_empty_string.rs new file mode 100644 index 0000000000000..9a8894b6c0d32 --- /dev/null +++ b/src/tools/clippy/tests/ui/writeln_empty_string.rs @@ -0,0 +1,20 @@ +// run-rustfix + +#![allow(unused_must_use)] +#![warn(clippy::writeln_empty_string)] +use std::io::Write; + +fn main() { + let mut v = Vec::new(); + + // These should fail + writeln!(&mut v, ""); + + let mut suggestion = Vec::new(); + writeln!(&mut suggestion, ""); + + // These should be fine + writeln!(&mut v); + writeln!(&mut v, " "); + write!(&mut v, ""); +} diff --git a/src/tools/clippy/tests/ui/writeln_empty_string.stderr b/src/tools/clippy/tests/ui/writeln_empty_string.stderr new file mode 100644 index 0000000000000..99635229b3e13 --- /dev/null +++ b/src/tools/clippy/tests/ui/writeln_empty_string.stderr @@ -0,0 +1,16 @@ +error: using `writeln!(&mut v, "")` + --> $DIR/writeln_empty_string.rs:11:5 + | +LL | writeln!(&mut v, ""); + | ^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(&mut v)` + | + = note: `-D clippy::writeln-empty-string` implied by `-D warnings` + +error: using `writeln!(&mut suggestion, "")` + --> $DIR/writeln_empty_string.rs:14:5 + | +LL | writeln!(&mut suggestion, ""); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(&mut suggestion)` + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/wrong_self_convention.rs b/src/tools/clippy/tests/ui/wrong_self_convention.rs new file mode 100644 index 0000000000000..99652ca4470c2 --- /dev/null +++ b/src/tools/clippy/tests/ui/wrong_self_convention.rs @@ -0,0 +1,77 @@ +#![warn(clippy::wrong_self_convention)] +#![warn(clippy::wrong_pub_self_convention)] +#![allow(dead_code)] + +fn main() {} + +#[derive(Clone, Copy)] +struct Foo; + +impl Foo { + fn as_i32(self) {} + fn as_u32(&self) {} + fn into_i32(self) {} + fn is_i32(self) {} + fn is_u32(&self) {} + fn to_i32(self) {} + fn from_i32(self) {} + + pub fn as_i64(self) {} + pub fn into_i64(self) {} + pub fn is_i64(self) {} + pub fn to_i64(self) {} + pub fn from_i64(self) {} + // check whether the lint can be allowed at the function level + #[allow(clippy::wrong_self_convention)] + pub fn from_cake(self) {} + + fn as_x>(_: F) {} + fn as_y>(_: F) {} +} + +struct Bar; + +impl Bar { + fn as_i32(self) {} + fn as_u32(&self) {} + fn into_i32(&self) {} + fn into_u32(self) {} + fn is_i32(self) {} + fn is_u32(&self) {} + fn to_i32(self) {} + fn to_u32(&self) {} + fn from_i32(self) {} + + pub fn as_i64(self) {} + pub fn into_i64(&self) {} + pub fn is_i64(self) {} + pub fn to_i64(self) {} + pub fn from_i64(self) {} + + // test for false positives + fn as_(self) {} + fn into_(&self) {} + fn is_(self) {} + fn to_(self) {} + fn from_(self) {} + fn to_mut(&mut self) {} +} + +// Allow Box, Rc, Arc for methods that take conventionally take Self by value +#[allow(clippy::boxed_local)] +mod issue4293 { + use std::rc::Rc; + use std::sync::Arc; + + struct T; + + impl T { + fn into_s1(self: Box) {} + fn into_s2(self: Rc) {} + fn into_s3(self: Arc) {} + + fn into_t1(self: Box) {} + fn into_t2(self: Rc) {} + fn into_t3(self: Arc) {} + } +} diff --git a/src/tools/clippy/tests/ui/wrong_self_convention.stderr b/src/tools/clippy/tests/ui/wrong_self_convention.stderr new file mode 100644 index 0000000000000..0d0eb19cd0723 --- /dev/null +++ b/src/tools/clippy/tests/ui/wrong_self_convention.stderr @@ -0,0 +1,76 @@ +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:17:17 + | +LL | fn from_i32(self) {} + | ^^^^ + | + = note: `-D clippy::wrong-self-convention` implied by `-D warnings` + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:23:21 + | +LL | pub fn from_i64(self) {} + | ^^^^ + +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:35:15 + | +LL | fn as_i32(self) {} + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:37:17 + | +LL | fn into_i32(&self) {} + | ^^^^^ + +error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:39:15 + | +LL | fn is_i32(self) {} + | ^^^^ + +error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:41:15 + | +LL | fn to_i32(self) {} + | ^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:43:17 + | +LL | fn from_i32(self) {} + | ^^^^ + +error: methods called `as_*` usually take self by reference or self by mutable reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:45:19 + | +LL | pub fn as_i64(self) {} + | ^^^^ + +error: methods called `into_*` usually take self by value; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:46:21 + | +LL | pub fn into_i64(&self) {} + | ^^^^^ + +error: methods called `is_*` usually take self by reference or no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:47:19 + | +LL | pub fn is_i64(self) {} + | ^^^^ + +error: methods called `to_*` usually take self by reference; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:48:19 + | +LL | pub fn to_i64(self) {} + | ^^^^ + +error: methods called `from_*` usually take no self; consider choosing a less ambiguous name + --> $DIR/wrong_self_convention.rs:49:21 + | +LL | pub fn from_i64(self) {} + | ^^^^ + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/zero_div_zero.rs b/src/tools/clippy/tests/ui/zero_div_zero.rs new file mode 100644 index 0000000000000..09db130a76431 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_div_zero.rs @@ -0,0 +1,13 @@ +#[allow(unused_variables)] +#[warn(clippy::zero_divided_by_zero)] +fn main() { + let nan = 0.0 / 0.0; + let f64_nan = 0.0 / 0.0f64; + let other_f64_nan = 0.0f64 / 0.0; + let one_more_f64_nan = 0.0f64 / 0.0f64; + let zero = 0.0; + let other_zero = 0.0; + let other_nan = zero / other_zero; // fine - this lint doesn't propegate constants. + let not_nan = 2.0 / 0.0; // not an error: 2/0 = inf + let also_not_nan = 0.0 / 2.0; // not an error: 0/2 = 0 +} diff --git a/src/tools/clippy/tests/ui/zero_div_zero.stderr b/src/tools/clippy/tests/ui/zero_div_zero.stderr new file mode 100644 index 0000000000000..d0e88f3c5a546 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_div_zero.stderr @@ -0,0 +1,61 @@ +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:4:15 + | +LL | let nan = 0.0 / 0.0; + | ^^^^^^^^^ + | + = note: `#[deny(clippy::eq_op)]` on by default + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:4:15 + | +LL | let nan = 0.0 / 0.0; + | ^^^^^^^^^ + | + = note: `-D clippy::zero-divided-by-zero` implied by `-D warnings` + = help: Consider using `f64::NAN` if you would like a constant representing NaN + +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:5:19 + | +LL | let f64_nan = 0.0 / 0.0f64; + | ^^^^^^^^^^^^ + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:5:19 + | +LL | let f64_nan = 0.0 / 0.0f64; + | ^^^^^^^^^^^^ + | + = help: Consider using `f64::NAN` if you would like a constant representing NaN + +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:6:25 + | +LL | let other_f64_nan = 0.0f64 / 0.0; + | ^^^^^^^^^^^^ + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:6:25 + | +LL | let other_f64_nan = 0.0f64 / 0.0; + | ^^^^^^^^^^^^ + | + = help: Consider using `f64::NAN` if you would like a constant representing NaN + +error: equal expressions as operands to `/` + --> $DIR/zero_div_zero.rs:7:28 + | +LL | let one_more_f64_nan = 0.0f64 / 0.0f64; + | ^^^^^^^^^^^^^^^ + +error: constant division of `0.0` with `0.0` will always result in NaN + --> $DIR/zero_div_zero.rs:7:28 + | +LL | let one_more_f64_nan = 0.0f64 / 0.0f64; + | ^^^^^^^^^^^^^^^ + | + = help: Consider using `f64::NAN` if you would like a constant representing NaN + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/zero_offset.rs b/src/tools/clippy/tests/ui/zero_offset.rs new file mode 100644 index 0000000000000..2de904376ad45 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_offset.rs @@ -0,0 +1,12 @@ +fn main() { + unsafe { + let x = &() as *const (); + x.offset(0); + x.wrapping_add(0); + x.sub(0); + x.wrapping_sub(0); + + let y = &1 as *const u8; + y.offset(0); + } +} diff --git a/src/tools/clippy/tests/ui/zero_offset.stderr b/src/tools/clippy/tests/ui/zero_offset.stderr new file mode 100644 index 0000000000000..cfcd7de2b3d2c --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_offset.stderr @@ -0,0 +1,9 @@ +error[E0606]: casting `&i32` as `*const u8` is invalid + --> $DIR/zero_offset.rs:9:17 + | +LL | let y = &1 as *const u8; + | ^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0606`. diff --git a/src/tools/clippy/tests/ui/zero_ptr.fixed b/src/tools/clippy/tests/ui/zero_ptr.fixed new file mode 100644 index 0000000000000..489aa4121a3a9 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_ptr.fixed @@ -0,0 +1,14 @@ +// run-rustfix +pub fn foo(_const: *const f32, _mut: *mut i64) {} + +fn main() { + let _ = std::ptr::null::(); + let _ = std::ptr::null_mut::(); + let _: *const u8 = std::ptr::null(); + + foo(0 as _, 0 as _); + foo(std::ptr::null(), std::ptr::null_mut()); + + let z = 0; + let _ = z as *const usize; // this is currently not caught +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.rs b/src/tools/clippy/tests/ui/zero_ptr.rs new file mode 100644 index 0000000000000..c3b55ef9ebd90 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_ptr.rs @@ -0,0 +1,14 @@ +// run-rustfix +pub fn foo(_const: *const f32, _mut: *mut i64) {} + +fn main() { + let _ = 0 as *const usize; + let _ = 0 as *mut f64; + let _: *const u8 = 0 as *const _; + + foo(0 as _, 0 as _); + foo(0 as *const _, 0 as *mut _); + + let z = 0; + let _ = z as *const usize; // this is currently not caught +} diff --git a/src/tools/clippy/tests/ui/zero_ptr.stderr b/src/tools/clippy/tests/ui/zero_ptr.stderr new file mode 100644 index 0000000000000..4ee5e9a261686 --- /dev/null +++ b/src/tools/clippy/tests/ui/zero_ptr.stderr @@ -0,0 +1,34 @@ +error: `0 as *const _` detected + --> $DIR/zero_ptr.rs:5:13 + | +LL | let _ = 0 as *const usize; + | ^^^^^^^^^^^^^^^^^ help: try: `std::ptr::null::()` + | + = note: `-D clippy::zero-ptr` implied by `-D warnings` + +error: `0 as *mut _` detected + --> $DIR/zero_ptr.rs:6:13 + | +LL | let _ = 0 as *mut f64; + | ^^^^^^^^^^^^^ help: try: `std::ptr::null_mut::()` + +error: `0 as *const _` detected + --> $DIR/zero_ptr.rs:7:24 + | +LL | let _: *const u8 = 0 as *const _; + | ^^^^^^^^^^^^^ help: try: `std::ptr::null()` + +error: `0 as *const _` detected + --> $DIR/zero_ptr.rs:10:9 + | +LL | foo(0 as *const _, 0 as *mut _); + | ^^^^^^^^^^^^^ help: try: `std::ptr::null()` + +error: `0 as *mut _` detected + --> $DIR/zero_ptr.rs:10:24 + | +LL | foo(0 as *const _, 0 as *mut _); + | ^^^^^^^^^^^ help: try: `std::ptr::null_mut()` + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/versioncheck.rs b/src/tools/clippy/tests/versioncheck.rs new file mode 100644 index 0000000000000..f5d03c645df04 --- /dev/null +++ b/src/tools/clippy/tests/versioncheck.rs @@ -0,0 +1,19 @@ +#[test] +fn check_that_clippy_lints_has_the_same_version_as_clippy() { + let clippy_meta = cargo_metadata::MetadataCommand::new() + .no_deps() + .exec() + .expect("could not obtain cargo metadata"); + std::env::set_current_dir(std::env::current_dir().unwrap().join("clippy_lints")).unwrap(); + let clippy_lints_meta = cargo_metadata::MetadataCommand::new() + .no_deps() + .exec() + .expect("could not obtain cargo metadata"); + assert_eq!(clippy_lints_meta.packages[0].version, clippy_meta.packages[0].version); + for package in &clippy_meta.packages[0].dependencies { + if package.name == "clippy_lints" { + assert!(package.req.matches(&clippy_lints_meta.packages[0].version)); + return; + } + } +} diff --git a/src/tools/clippy/triagebot.toml b/src/tools/clippy/triagebot.toml new file mode 100644 index 0000000000000..411229935c36a --- /dev/null +++ b/src/tools/clippy/triagebot.toml @@ -0,0 +1,7 @@ +[relabel] +allow-unauthenticated = [ + "C-*", "A-*", "E-*", "L-*", "M-*", "O-*", + "good first issue", "needs test" +] + +[assign] diff --git a/src/tools/clippy/util/cov.sh b/src/tools/clippy/util/cov.sh new file mode 100755 index 0000000000000..3f9a6b06f7255 --- /dev/null +++ b/src/tools/clippy/util/cov.sh @@ -0,0 +1,37 @@ +#!/usr/bin/bash + +# This run `kcov` on Clippy. The coverage report will be at +# `./target/cov/index.html`. +# `compile-test` is special. `kcov` does not work directly on it so these files +# are compiled manually. + +tests=$(find tests/ -maxdepth 1 -name '*.rs' ! -name compile-test.rs -exec basename {} .rs \;) +tmpdir=$(mktemp -d) + +cargo test --no-run --verbose + +for t in $tests; do + kcov \ + --verify \ + --include-path="$(pwd)/src,$(pwd)/clippy_lints/src" \ + "$tmpdir/$t" \ + cargo test --test "$t" +done + +for t in ./tests/compile-fail/*.rs; do + kcov \ + --verify \ + --include-path="$(pwd)/src,$(pwd)/clippy_lints/src" \ + "$tmpdir/compile-fail-$(basename "$t")" \ + cargo run -- -L target/debug -L target/debug/deps -Z no-trans "$t" +done + +for t in ./tests/run-pass/*.rs; do + kcov \ + --verify \ + --include-path="$(pwd)/src,$(pwd)/clippy_lints/src" \ + "$tmpdir/run-pass-$(basename "$t")" \ + cargo run -- -L target/debug -L target/debug/deps -Z no-trans "$t" +done + +kcov --verify --merge target/cov "$tmpdir"/* diff --git a/src/tools/clippy/util/export.py b/src/tools/clippy/util/export.py new file mode 100755 index 0000000000000..5d1bd60acf3d6 --- /dev/null +++ b/src/tools/clippy/util/export.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# Build the gh-pages + +from collections import OrderedDict +import re +import sys +import json + +from lintlib import parse_all, log + +lint_subheadline = re.compile(r'''^\*\*([\w\s]+?)[:?.!]?\*\*(.*)''') +rust_code_block = re.compile(r'''```rust.+?```''', flags=re.DOTALL) + +CONF_TEMPLATE = """\ +This lint has the following configuration variables: + +* `%s: %s`: %s (defaults to `%s`).""" + + +def parse_code_block(match): + lines = [] + + for line in match.group(0).split('\n'): + if not line.startswith('# '): + lines.append(line) + + return '\n'.join(lines) + + +def parse_lint_def(lint): + lint_dict = {} + lint_dict['id'] = lint.name + lint_dict['group'] = lint.group + lint_dict['level'] = lint.level + lint_dict['docs'] = OrderedDict() + + last_section = None + + for line in lint.doc: + match = re.match(lint_subheadline, line) + if match: + last_section = match.groups()[0] + text = match.groups()[1] + else: + text = line + + if not last_section: + log.warning("Skipping comment line as it was not preceded by a heading") + log.debug("in lint `%s`, line `%s`", lint.name, line) + + if last_section not in lint_dict['docs']: + lint_dict['docs'][last_section] = "" + + lint_dict['docs'][last_section] += text + "\n" + + for section in lint_dict['docs']: + lint_dict['docs'][section] = re.sub(rust_code_block, parse_code_block, lint_dict['docs'][section].strip()) + + return lint_dict + + +def main(): + lintlist, configs = parse_all() + lints = {} + for lint in lintlist: + lints[lint.name] = parse_lint_def(lint) + if lint.name in configs: + lints[lint.name]['docs']['Configuration'] = \ + CONF_TEMPLATE % configs[lint.name] + + outfile = sys.argv[1] if len(sys.argv) > 1 else "util/gh-pages/lints.json" + with open(outfile, "w") as fp: + lints = list(lints.values()) + lints.sort(key=lambda x: x['id']) + json.dump(lints, fp, indent=2) + log.info("wrote JSON for great justice") + + +if __name__ == "__main__": + main() diff --git a/src/tools/clippy/util/fetch_prs_between.sh b/src/tools/clippy/util/fetch_prs_between.sh new file mode 100755 index 0000000000000..6865abf971b28 --- /dev/null +++ b/src/tools/clippy/util/fetch_prs_between.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Fetches the merge commits between two git commits and prints the PR URL +# together with the full commit message +# +# If you want to use this to update the Clippy changelog, be sure to manually +# exclude the non-user facing changes like 'rustup' PRs, typo fixes, etc. + +first=$1 +last=$2 + +IFS=' +' +for pr in $(git log --oneline --grep "Merge #" --grep "Merge pull request" --grep "Auto merge of" --grep "Rollup merge of" "$first...$last" | sort -rn | uniq); do + id=$(echo "$pr" | rg -o '#[0-9]{3,5}' | cut -c 2-) + commit=$(echo "$pr" | cut -d' ' -f 1) + message=$(git --no-pager show --pretty=medium "$commit") + if [[ -n $(echo "$message" | rg "^[\s]{4}changelog: [nN]one\.*$") ]]; then + continue + fi + + echo "URL: https://github.com/rust-lang/rust-clippy/pull/$id" + echo "Markdown URL: [#$id](https://github.com/rust-lang/rust-clippy/pull/$id)" + echo "$message" + echo "---------------------------------------------------------" + echo +done diff --git a/src/tools/clippy/util/gh-pages/index.html b/src/tools/clippy/util/gh-pages/index.html new file mode 100644 index 0000000000000..e11f2eeba3b32 --- /dev/null +++ b/src/tools/clippy/util/gh-pages/index.html @@ -0,0 +1,277 @@ + + + + + + + ALL the Clippy Lints + + + + + + + + + + Fork me on Github + + + + + + + + + diff --git a/src/tools/clippy/util/gh-pages/versions.html b/src/tools/clippy/util/gh-pages/versions.html new file mode 100644 index 0000000000000..6e810a349bfcc --- /dev/null +++ b/src/tools/clippy/util/gh-pages/versions.html @@ -0,0 +1,91 @@ + + + + + + + Clippy lints documentation + + + + + +
+ + +
+ + + + +
+
+ + + + + + + + + + diff --git a/src/tools/clippy/util/lintlib.py b/src/tools/clippy/util/lintlib.py new file mode 100644 index 0000000000000..d0d9beb9b2d9f --- /dev/null +++ b/src/tools/clippy/util/lintlib.py @@ -0,0 +1,114 @@ +# Common utils for the several housekeeping scripts. + +import os +import re +import collections + +import logging as log +log.basicConfig(level=log.INFO, format='%(levelname)s: %(message)s') + +Lint = collections.namedtuple('Lint', 'name level doc sourcefile group') +Config = collections.namedtuple('Config', 'name ty doc default') + +lintname_re = re.compile(r'''pub\s+([A-Z_][A-Z_0-9]*)''') +group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''') +conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE) +confvar_re = re.compile( + r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\([^,]+,\s+"([^"]+)":\s+([^,]+),\s+([^\.\)]+).*\),''', re.MULTILINE) +comment_re = re.compile(r'''\s*/// ?(.*)''') + +lint_levels = { + "correctness": 'Deny', + "style": 'Warn', + "complexity": 'Warn', + "perf": 'Warn', + "restriction": 'Allow', + "pedantic": 'Allow', + "nursery": 'Allow', + "cargo": 'Allow', +} + + +def parse_lints(lints, filepath): + comment = [] + clippy = False + deprecated = False + name = "" + + with open(filepath) as fp: + for line in fp: + if clippy or deprecated: + m = lintname_re.search(line) + if m: + name = m.group(1).lower() + line = next(fp) + + if deprecated: + level = "Deprecated" + group = "deprecated" + else: + while True: + g = group_re.search(line) + if g: + group = g.group(1).lower() + level = lint_levels.get(group, None) + break + line = next(fp) + + if level is None: + continue + + log.info("found %s with level %s in %s", + name, level, filepath) + lints.append(Lint(name, level, comment, filepath, group)) + comment = [] + + clippy = False + deprecated = False + name = "" + else: + m = comment_re.search(line) + if m: + comment.append(m.group(1)) + elif line.startswith("declare_clippy_lint!"): + clippy = True + deprecated = False + elif line.startswith("declare_deprecated_lint!"): + clippy = False + deprecated = True + elif line.startswith("declare_lint!"): + import sys + print( + "don't use `declare_lint!` in Clippy, " + "use `declare_clippy_lint!` instead" + ) + sys.exit(42) + + +def parse_configs(path): + configs = {} + with open(os.path.join(path, 'utils/conf.rs')) as fp: + contents = fp.read() + + match = re.search(conf_re, contents) + confvars = re.findall(confvar_re, match.group(1)) + + for (lints, doc, name, ty, default) in confvars: + for lint in lints.split(','): + configs[lint.strip().lower()] = Config(name.replace("_", "-"), ty, doc, default) + return configs + + +def parse_all(path="clippy_lints/src"): + lints = [] + for root, dirs, files in os.walk(path): + for fn in files: + if fn.endswith('.rs'): + parse_lints(lints, os.path.join(root, fn)) + + log.info("got %s lints", len(lints)) + + configs = parse_configs(path) + log.info("got %d configs", len(configs)) + + return lints, configs diff --git a/src/tools/clippy/util/versions.py b/src/tools/clippy/util/versions.py new file mode 100755 index 0000000000000..5cdc7313f5439 --- /dev/null +++ b/src/tools/clippy/util/versions.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +import json +import os +import sys + +from lintlib import log + + +def key(v): + if v == 'master': + return float('inf') + if v == 'stable': + return sys.maxsize + if v == 'beta': + return sys.maxsize - 1 + + v = v.replace('v', '').replace('rust-', '') + + s = 0 + for i, val in enumerate(v.split('.')[::-1]): + s += int(val) * 100**i + + return s + + +def main(): + if len(sys.argv) < 2: + print("Error: specify output directory") + return + + outdir = sys.argv[1] + versions = [ + dir for dir in os.listdir(outdir) if not dir.startswith(".") and os.path.isdir(os.path.join(outdir, dir)) + ] + versions.sort(key=key) + + with open(os.path.join(outdir, "versions.json"), "w") as fp: + json.dump(versions, fp, indent=2) + log.info("wrote JSON for great justice") + + +if __name__ == "__main__": + main()