Add 'src/tools/clippy/' from commit 'd2708873ef711ec8ab45df1e984ecf24a96cd369'
git-subtree-dir: src/tools/clippy git-subtree-mainline:06c44816c1
git-subtree-split:d2708873ef
This commit is contained in:
commit
bce9fae97a
1286 changed files with 114475 additions and 0 deletions
6
src/tools/clippy/.cargo/config
Normal file
6
src/tools/clippy/.cargo/config
Normal file
|
@ -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"]
|
19
src/tools/clippy/.editorconfig
Normal file
19
src/tools/clippy/.editorconfig
Normal file
|
@ -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
|
3
src/tools/clippy/.gitattributes
vendored
Normal file
3
src/tools/clippy/.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
* text=auto eol=lf
|
||||
*.rs text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4
|
||||
*.fixed linguist-language=Rust
|
8
src/tools/clippy/.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
src/tools/clippy/.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!--
|
||||
Hi there! Whether you've come to make a suggestion for a new lint, an improvement to an existing lint or to report a bug or a false positive in Clippy, you've come to the right place.
|
||||
|
||||
For bug reports and false positives, please include the output of `cargo clippy -V` in the report.
|
||||
|
||||
Thank you for using Clippy!
|
||||
|
||||
Write your comment below this line: -->
|
31
src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
31
src/tools/clippy/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -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
|
57
src/tools/clippy/.github/deploy.sh
vendored
Normal file
57
src/tools/clippy/.github/deploy.sh
vendored
Normal file
|
@ -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"
|
29
src/tools/clippy/.github/driver.sh
vendored
Normal file
29
src/tools/clippy/.github/driver.sh
vendored
Normal file
|
@ -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
|
99
src/tools/clippy/.github/workflows/clippy.yml
vendored
Normal file
99
src/tools/clippy/.github/workflows/clippy.yml
vendored
Normal file
|
@ -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
|
329
src/tools/clippy/.github/workflows/clippy_bors.yml
vendored
Normal file
329
src/tools/clippy/.github/workflows/clippy_bors.yml
vendored
Normal file
|
@ -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
|
74
src/tools/clippy/.github/workflows/clippy_dev.yml
vendored
Normal file
74
src/tools/clippy/.github/workflows/clippy_dev.yml
vendored
Normal file
|
@ -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
|
51
src/tools/clippy/.github/workflows/deploy.yml
vendored
Normal file
51
src/tools/clippy/.github/workflows/deploy.yml
vendored
Normal file
|
@ -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
|
55
src/tools/clippy/.github/workflows/remark.yml
vendored
Normal file
55
src/tools/clippy/.github/workflows/remark.yml
vendored
Normal file
|
@ -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
|
37
src/tools/clippy/.gitignore
vendored
Normal file
37
src/tools/clippy/.gitignore
vendored
Normal file
|
@ -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
|
12
src/tools/clippy/.remarkrc
Normal file
12
src/tools/clippy/.remarkrc
Normal file
|
@ -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
|
||||
}
|
||||
}
|
1657
src/tools/clippy/CHANGELOG.md
Normal file
1657
src/tools/clippy/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
70
src/tools/clippy/CODE_OF_CONDUCT.md
Normal file
70
src/tools/clippy/CODE_OF_CONDUCT.md
Normal file
|
@ -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 <a href="http://citizencodeofconduct.org/">Citizen
|
||||
Code of Conduct</a>; 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
|
243
src/tools/clippy/CONTRIBUTING.md
Normal file
243
src/tools/clippy/CONTRIBUTING.md
Normal file
|
@ -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.
|
||||
|
||||
<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
|
||||
|
||||
[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0
|
||||
[MIT]: https://opensource.org/licenses/MIT
|
7
src/tools/clippy/COPYRIGHT
Normal file
7
src/tools/clippy/COPYRIGHT
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright 2014-2020 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
option. All files in the project carrying such notice may not be
|
||||
copied, modified, or distributed except according to those terms.
|
59
src/tools/clippy/Cargo.toml
Normal file
59
src/tools/clippy/Cargo.toml
Normal file
|
@ -0,0 +1,59 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
version = "0.0.212"
|
||||
authors = [
|
||||
"Manish Goregaokar <manishsmail@gmail.com>",
|
||||
"Andre Bogus <bogusandre@gmail.com>",
|
||||
"Georg Brandl <georg@python.org>",
|
||||
"Martin Carton <cartonmartin@gmail.com>",
|
||||
"Oliver Schneider <clippy-iethah7aipeen8neex1a@oli-obk.de>"
|
||||
]
|
||||
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"]
|
201
src/tools/clippy/LICENSE-APACHE
Normal file
201
src/tools/clippy/LICENSE-APACHE
Normal file
|
@ -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.
|
27
src/tools/clippy/LICENSE-MIT
Normal file
27
src/tools/clippy/LICENSE-MIT
Normal file
|
@ -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.
|
193
src/tools/clippy/README.md
Normal file
193
src/tools/clippy/README.md
Normal file
|
@ -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 <LICENSE-APACHE or
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license
|
||||
<LICENSE-MIT or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)>, at your
|
||||
option. Files in the project may not be
|
||||
copied, modified, or distributed except according to those terms.
|
19
src/tools/clippy/build.rs
Normal file
19
src/tools/clippy/build.rs
Normal file
|
@ -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()
|
||||
);
|
||||
}
|
17
src/tools/clippy/clippy_dev/Cargo.toml
Normal file
17
src/tools/clippy/clippy_dev/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "clippy_dev"
|
||||
version = "0.0.1"
|
||||
authors = ["Philipp Hansch <dev@phansch.net>"]
|
||||
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 = []
|
175
src/tools/clippy/clippy_dev/src/fmt.rs
Normal file
175
src/tools/clippy/clippy_dev/src/fmt.rs
Normal file
|
@ -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<io::Error> for CliError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
Self::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<walkdir::Error> 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<bool, CliError> {
|
||||
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<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> 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<OsStr>,
|
||||
dir: impl AsRef<Path>,
|
||||
args: &[impl AsRef<OsStr>],
|
||||
) -> Result<bool, CliError> {
|
||||
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<bool, CliError> {
|
||||
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<bool, CliError> {
|
||||
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)
|
||||
}
|
524
src/tools/clippy/clippy_dev/src/lib.rs
Normal file
524
src/tools/clippy/clippy_dev/src/lib.rs
Normal file
|
@ -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<name>[A-Z_][A-Z_0-9]*)\s*,\s*
|
||||
(?P<cat>[a-z_]+)\s*,\s*
|
||||
"(?P<desc>(?:[^"\\]+|\\(?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<name>[A-Z_][A-Z_0-9]*)\s*,\s*
|
||||
"(?P<desc>(?:[^"\\]+|\\(?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<String>,
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
lints.iter().filter(|l| l.group == "internal").cloned().collect()
|
||||
}
|
||||
|
||||
/// Returns all deprecated lints
|
||||
#[must_use]
|
||||
pub fn deprecated_lints(lints: &[Self]) -> Vec<Self> {
|
||||
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<Item = Self>) -> HashMap<String, Vec<Self>> {
|
||||
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<Item = &'a Lint>) -> Vec<String> {
|
||||
lints
|
||||
.map(|l| format!(" LintId::of(&{}::{}),", l.module, l.name.to_uppercase()))
|
||||
.sorted()
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
/// Generates the `pub mod module_name` list in `clippy_lints/src/lib.rs`.
|
||||
#[must_use]
|
||||
pub fn gen_modules_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
|
||||
lints
|
||||
.map(|l| &l.module)
|
||||
.unique()
|
||||
.map(|module| format!("mod {};", module))
|
||||
.sorted()
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
/// Generates the list of lint links at the bottom of the README
|
||||
#[must_use]
|
||||
pub fn gen_changelog_lint_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
|
||||
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<Item = &'a Lint>) -> Vec<String> {
|
||||
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::<Vec<String>>()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn gen_register_lint_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
|
||||
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::<Vec<String>>();
|
||||
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<Item = Lint> {
|
||||
lint_files().flat_map(|f| gather_from_file(&f))
|
||||
}
|
||||
|
||||
fn gather_from_file(dir_entry: &walkdir::DirEntry) -> impl Iterator<Item = Lint> {
|
||||
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::<Vec<_>>()
|
||||
.join("::");
|
||||
|
||||
parse_contents(&content, &module)
|
||||
}
|
||||
|
||||
fn parse_contents(content: &str, module: &str) -> impl Iterator<Item = Lint> {
|
||||
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::<Vec<Lint>>().into_iter()` causes some lifetime issues due to the map
|
||||
lints.chain(deprecated).collect::<Vec<Lint>>().into_iter()
|
||||
}
|
||||
|
||||
/// Collects all .rs files in the `clippy_lints/src` directory
|
||||
fn lint_files() -> impl Iterator<Item = walkdir::DirEntry> {
|
||||
// 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<F>(
|
||||
path: &Path,
|
||||
start: &str,
|
||||
end: &str,
|
||||
replace_start: bool,
|
||||
write_back: bool,
|
||||
replacements: F,
|
||||
) -> FileChange
|
||||
where
|
||||
F: FnOnce() -> Vec<String>,
|
||||
{
|
||||
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<String>` 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<F>(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange
|
||||
where
|
||||
F: FnOnce() -> Vec<String>,
|
||||
{
|
||||
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<Lint> = 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<String, Vec<Lint>> = 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<String> = 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()));
|
||||
}
|
120
src/tools/clippy/clippy_dev/src/main.rs
Normal file
120
src/tools/clippy/clippy_dev/src/main.rs
Normal file
|
@ -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();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
177
src/tools/clippy/clippy_dev/src/new_lint.rs
Normal file
177
src/tools/clippy/clippy_dev/src/new_lint.rs
Normal file
|
@ -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");
|
||||
}
|
51
src/tools/clippy/clippy_dev/src/stderr_length_check.rs
Normal file
51
src/tools/clippy/clippy_dev/src/stderr_length_check.rs
Normal file
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
162
src/tools/clippy/clippy_dev/src/update_lints.rs
Normal file
162
src/tools/clippy/clippy_dev/src/update_lints.rs
Normal file
|
@ -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<Lint> = 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<Lint> = vec!{:#?};", sorted_usable_lints)
|
||||
.lines()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
)
|
||||
.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"),
|
||||
"<!-- begin autogenerated links to lint list -->",
|
||||
"<!-- end autogenerated links to lint list -->",
|
||||
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<Lint> = 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
|
||||
}
|
17
src/tools/clippy/clippy_dummy/Cargo.toml
Normal file
17
src/tools/clippy/clippy_dummy/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "clippy_dummy" # rename to clippy before publishing
|
||||
version = "0.0.303"
|
||||
authors = ["Manish Goregaokar <manishsmail@gmail.com>"]
|
||||
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"
|
6
src/tools/clippy/clippy_dummy/PUBLISH.md
Normal file
6
src/tools/clippy/clippy_dummy/PUBLISH.md
Normal file
|
@ -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.
|
42
src/tools/clippy/clippy_dummy/build.rs
Normal file
42
src/tools/clippy/clippy_dummy/build.rs
Normal file
|
@ -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(())
|
||||
}
|
9
src/tools/clippy/clippy_dummy/crates-readme.md
Normal file
9
src/tools/clippy/clippy_dummy/crates-readme.md
Normal file
|
@ -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
|
3
src/tools/clippy/clippy_dummy/src/main.rs
Normal file
3
src/tools/clippy/clippy_dummy/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
panic!("This shouldn't even compile")
|
||||
}
|
37
src/tools/clippy/clippy_lints/Cargo.toml
Normal file
37
src/tools/clippy/clippy_lints/Cargo.toml
Normal file
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
# begin automatic update
|
||||
version = "0.0.212"
|
||||
# end automatic update
|
||||
authors = [
|
||||
"Manish Goregaokar <manishsmail@gmail.com>",
|
||||
"Andre Bogus <bogusandre@gmail.com>",
|
||||
"Georg Brandl <georg@python.org>",
|
||||
"Martin Carton <cartonmartin@gmail.com>"
|
||||
]
|
||||
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 <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
|
||||
url = { version = "2.1.0", features = ["serde"] }
|
||||
|
||||
[features]
|
||||
deny-warnings = []
|
1
src/tools/clippy/clippy_lints/README.md
Normal file
1
src/tools/clippy/clippy_lints/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
This crate contains Clippy lints. For the main crate, check [GitHub](https://github.com/rust-lang/rust-clippy).
|
117
src/tools/clippy/clippy_lints/src/approx_const.rs
Normal file
117
src/tools/clippy/clippy_lints/src/approx_const.rs
Normal file
|
@ -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::<f64>().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
|
||||
}
|
||||
}
|
149
src/tools/clippy/clippy_lints/src/arithmetic.rs
Normal file
149
src/tools/clippy/clippy_lints/src/arithmetic.rs
Normal file
|
@ -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<Span>,
|
||||
/// This field is used to check whether expressions are constants, such as in enum discriminants
|
||||
/// and consts
|
||||
const_span: Option<Span>,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
58
src/tools/clippy/clippy_lints/src/as_conversions.rs
Normal file
58
src/tools/clippy/clippy_lints/src/as_conversions.rs
Normal file
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
152
src/tools/clippy/clippy_lints/src/assertions_on_constants.rs
Normal file
152
src/tools/clippy/clippy_lints/src/assertions_on_constants.rs
Normal file
|
@ -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<AssertKind> {
|
||||
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
|
||||
}
|
261
src/tools/clippy/clippy_lints/src/assign_ops.rs
Normal file
261
src/tools/clippy/clippy_lints/src/assign_ops.rs
Normal file
|
@ -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<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
135
src/tools/clippy/clippy_lints/src/atomic_ordering.rs
Normal file
135
src/tools/clippy/clippy_lints/src/atomic_ordering.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
657
src/tools/clippy/clippy_lints/src/attrs.rs
Normal file
657
src/tools/clippy/clippy_lints/src/attrs.rs
Normal file
|
@ -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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
97
src/tools/clippy/clippy_lints/src/await_holding_lock.rs
Normal file
97
src/tools/clippy/clippy_lints/src/await_holding_lock.rs
Normal file
|
@ -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<u32>) {
|
||||
/// let guard = x.lock().unwrap();
|
||||
/// *guard += 1;
|
||||
/// bar.await;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust,ignore
|
||||
/// use std::sync::Mutex;
|
||||
///
|
||||
/// async fn foo(x: &Mutex<u32>) {
|
||||
/// {
|
||||
/// 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)
|
||||
}
|
326
src/tools/clippy/clippy_lints/src/bit_mask.rs
Normal file
326
src/tools/clippy/clippy_lints/src/bit_mask.rs
Normal file
|
@ -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 `_ <bit_op> m
|
||||
/// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` 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<u128> {
|
||||
match constant(cx, cx.tables, lit)?.0 {
|
||||
Constant::Int(n) => Some(n),
|
||||
_ => None,
|
||||
}
|
||||
}
|
51
src/tools/clippy/clippy_lints/src/blacklisted_name.rs
Normal file
51
src/tools/clippy/clippy_lints/src/blacklisted_name.rs
Normal file
|
@ -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<String>,
|
||||
}
|
||||
|
||||
impl BlacklistedName {
|
||||
pub fn new(blacklist: FxHashSet<String>) -> 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
148
src/tools/clippy/clippy_lints/src/block_in_if_condition.rs
Normal file
148
src/tools/clippy/clippy_lints/src/block_in_if_condition.rs
Normal file
|
@ -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<Self::Map> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
499
src/tools/clippy/clippy_lints/src/booleans.rs
Normal file
499
src/tools/clippy/clippy_lints/src/booleans.rs
Normal file
|
@ -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<Bool>) -> Result<Vec<Bool>, 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<Bool, String> {
|
||||
fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
|
||||
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<String> {
|
||||
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<Self::Map> {
|
||||
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<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
117
src/tools/clippy/clippy_lints/src/bytecount.rs
Normal file
117
src/tools/clippy/clippy_lints/src/bytecount.rs
Normal file
|
@ -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 `<slice>.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<Name> {
|
||||
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,
|
||||
}
|
||||
}
|
105
src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs
Normal file
105
src/tools/clippy/clippy_lints/src/cargo_common_metadata.rs
Normal file
|
@ -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<String>) -> bool {
|
||||
value.as_ref().map_or(true, String::is_empty)
|
||||
}
|
||||
|
||||
fn is_empty_path(value: &Option<PathBuf>) -> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
345
src/tools/clippy/clippy_lints/src/checked_conversions.rs
Normal file
345
src/tools/clippy/clippy_lints/src/checked_conversions.rs
Normal file
|
@ -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<Conversion<'tcx>> {
|
||||
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<Conversion<'a>> {
|
||||
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<Conversion<'a>> {
|
||||
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<Conversion<'a>> {
|
||||
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<Self> {
|
||||
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<Conversion<'tcx>> {
|
||||
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<Conversion<'tcx>> {
|
||||
fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option<Conversion<'a>> {
|
||||
(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<Conversion<'a>> {
|
||||
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<Conversion<'a>> {
|
||||
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<T>, Option<U>) -> Option<(T, U)>
|
||||
fn transpose<T, U>(lhs: Option<T>, rhs: Option<U>) -> 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"];
|
163
src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
Normal file
163
src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
Normal file
|
@ -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<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
170
src/tools/clippy/clippy_lints/src/collapsible_if.rs
Normal file
170
src/tools/clippy/clippy_lints/src/collapsible_if.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
118
src/tools/clippy/clippy_lints/src/comparison_chain.rs
Normal file
118
src/tools/clippy/clippy_lints/src/comparison_chain.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
559
src/tools/clippy/clippy_lints/src/consts.rs
Normal file
559
src/tools/clippy/clippy_lints/src/consts.rs
Normal file
|
@ -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<Vec<u8>>),
|
||||
/// 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<Constant>),
|
||||
/// Also an array, but with only one constant, repeated N times.
|
||||
Repeat(Box<Constant>, u64),
|
||||
/// A tuple of constants.
|
||||
Tuple(Vec<Constant>),
|
||||
/// 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<H>(&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<Ordering> {
|
||||
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<Ty<'_>>) -> 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> {
|
||||
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<Constant> {
|
||||
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 {
|
||||
"<impl i8>" => i8::max_value() as u128,
|
||||
"<impl i16>" => i16::max_value() as u128,
|
||||
"<impl i32>" => i32::max_value() as u128,
|
||||
"<impl i64>" => i64::max_value() as u128,
|
||||
"<impl i128>" => 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<Constant> {
|
||||
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<Constant> {
|
||||
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<Constant>> {
|
||||
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
|
||||
}
|
||||
|
||||
/// Lookup a possibly constant expression from a `ExprKind::Path`.
|
||||
fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'cc>) -> Option<Constant> {
|
||||
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<Constant> {
|
||||
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<Constant> {
|
||||
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<Constant> {
|
||||
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<Constant> {
|
||||
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<Constant> {
|
||||
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::<Option<Vec<Constant>>>()
|
||||
.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::<Option<Vec<Constant>>>()
|
||||
.map(Constant::Vec),
|
||||
_ => None,
|
||||
},
|
||||
// FIXME: implement other array type conversions.
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
// FIXME: implement other conversions.
|
||||
_ => None,
|
||||
}
|
||||
}
|
417
src/tools/clippy/clippy_lints/src/copies.rs
Normal file
417
src/tools/clippy/clippy_lints/src/copies.rs
Normal file
|
@ -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<Symbol, Ty<'tcx>>,
|
||||
rhs: &FxHashMap<Symbol, Ty<'tcx>>,
|
||||
) -> 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, "<pat1>");
|
||||
let rhs = snippet(cx, j.pat.span, "<pat2>");
|
||||
|
||||
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<Symbol, Ty<'tcx>> {
|
||||
fn bindings_impl<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) {
|
||||
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<T, Eq>(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<T, Hash, Eq>(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
|
||||
}
|
55
src/tools/clippy/clippy_lints/src/copy_iterator.rs
Normal file
55
src/tools/clippy/clippy_lints/src/copy_iterator.rs
Normal file
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
src/tools/clippy/clippy_lints/src/dbg_macro.rs
Normal file
65
src/tools/clippy/clippy_lints/src/dbg_macro.rs
Normal file
|
@ -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<Span> {
|
||||
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)
|
||||
}
|
76
src/tools/clippy/clippy_lints/src/default_trait_access.rs
Normal file
76
src/tools/clippy/clippy_lints/src/default_trait_access.rs
Normal file
|
@ -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 <Foo as Default>::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(..) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
src/tools/clippy/clippy_lints/src/deprecated_lints.rs
Normal file
157
src/tools/clippy/clippy_lints/src/deprecated_lints.rs
Normal file
|
@ -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"
|
||||
}
|
113
src/tools/clippy/clippy_lints/src/dereference.rs
Normal file
113
src/tools/clippy/clippy_lints/src/dereference.rs
Normal file
|
@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
309
src/tools/clippy/clippy_lints/src/derive.rs
Normal file
309
src/tools/clippy/clippy_lints/src/derive.rs
Normal file
|
@ -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<Foo> for Foo`
|
||||
// For `impl PartialEq<B> 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<Self::Map> {
|
||||
NestedVisitorMap::All(self.cx.tcx.hir())
|
||||
}
|
||||
}
|
525
src/tools/clippy/clippy_lints/src/doc.rs
Normal file
525
src/tools/clippy/clippy_lints/src/doc.rs
Normal file
|
@ -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<String> {
|
||||
/// 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<String>,
|
||||
in_trait_impl: bool,
|
||||
}
|
||||
|
||||
impl DocMarkdown {
|
||||
pub fn new(valid_idents: FxHashSet<String>) -> 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<MultiSpan> + Copy,
|
||||
sig: &hir::FnSig<'_>,
|
||||
headers: DocHeaders,
|
||||
body_id: Option<hir::BodyId>,
|
||||
) {
|
||||
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<String>, 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<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
|
||||
cx: &LateContext<'_, '_>,
|
||||
valid_idents: &FxHashSet<String>,
|
||||
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 `<http://example.com>`
|
||||
// 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<String>, 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),
|
||||
);
|
||||
}
|
||||
}
|
95
src/tools/clippy/clippy_lints/src/double_comparison.rs
Normal file
95
src/tools/clippy/clippy_lints/src/double_comparison.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
75
src/tools/clippy/clippy_lints/src/double_parens.rs
Normal file
75
src/tools/clippy/clippy_lints/src/double_parens.rs
Normal file
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
69
src/tools/clippy/clippy_lints/src/drop_bounds.rs
Normal file
69
src/tools/clippy/clippy_lints/src/drop_bounds.rs
Normal file
|
@ -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<T: Drop>() {}
|
||||
/// ```
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
160
src/tools/clippy/clippy_lints/src/drop_forget_ref.rs
Normal file
160
src/tools/clippy/clippy_lints/src/drop_forget_ref.rs
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
src/tools/clippy/clippy_lints/src/duration_subsec.rs
Normal file
65
src/tools/clippy/clippy_lints/src/duration_subsec.rs
Normal file
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
src/tools/clippy/clippy_lints/src/else_if_without_else.rs
Normal file
72
src/tools/clippy/clippy_lints/src/else_if_without_else.rs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
60
src/tools/clippy/clippy_lints/src/empty_enum.rs
Normal file
60
src/tools/clippy/clippy_lints/src/empty_enum.rs
Normal file
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
187
src/tools/clippy/clippy_lints/src/entry.rs
Normal file
187
src/tools/clippy/clippy_lints/src/entry.rs
Normal file
|
@ -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<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
82
src/tools/clippy/clippy_lints/src/enum_clike.rs
Normal file
82
src/tools/clippy/clippy_lints/src/enum_clike.rs
Normal file
|
@ -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",
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
305
src/tools/clippy/clippy_lints/src/enum_variants.rs
Normal file
305
src/tools/clippy/clippy_lints/src/enum_variants.rs
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
229
src/tools/clippy/clippy_lints/src/eq_op.rs
Normal file
229
src/tools/clippy/clippy_lints/src/eq_op.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
59
src/tools/clippy/clippy_lints/src/erasing_op.rs
Normal file
59
src/tools/clippy/clippy_lints/src/erasing_op.rs
Normal file
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
164
src/tools/clippy/clippy_lints/src/escape.rs
Normal file
164
src/tools/clippy/clippy_lints/src/escape.rs
Normal file
|
@ -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<T>` 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<T>` 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
|
||||
}
|
||||
}
|
||||
}
|
227
src/tools/clippy/clippy_lints/src/eta_reduction.rs
Normal file
227
src/tools/clippy/clippy_lints/src/eta_reduction.rs
Normal file
|
@ -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<String> {
|
||||
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<Item = &Param<'_>>,
|
||||
call_args: &mut dyn Iterator<Item = &Expr<'_>>,
|
||||
) -> 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
|
||||
}
|
355
src/tools/clippy/clippy_lints/src/eval_order_dependence.rs
Normal file
355
src/tools/clippy/clippy_lints/src/eval_order_dependence.rs
Normal file
|
@ -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<Self::Map> {
|
||||
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<Self::Map> {
|
||||
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
|
||||
}
|
176
src/tools/clippy/clippy_lints/src/excessive_bools.rs
Normal file
176
src/tools/clippy/clippy_lints/src/excessive_bools.rs
Normal file
|
@ -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),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
47
src/tools/clippy/clippy_lints/src/exit.rs
Normal file
47
src/tools/clippy/clippy_lints/src/exit.rs
Normal file
|
@ -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`");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
src/tools/clippy/clippy_lints/src/explicit_write.rs
Normal file
149
src/tools/clippy/clippy_lints/src/explicit_write.rs
Normal file
|
@ -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<String> {
|
||||
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
|
||||
}
|
130
src/tools/clippy/clippy_lints/src/fallible_impl_from.rs
Normal file
130
src/tools/clippy/clippy_lints/src/fallible_impl_from.rs
Normal file
|
@ -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<String> 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<Span>,
|
||||
}
|
||||
|
||||
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<Self::Map> {
|
||||
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)");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
182
src/tools/clippy/clippy_lints/src/float_literal.rs
Normal file
182
src/tools/clippy/clippy_lints/src/float_literal.rs
Normal file
|
@ -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::<f32>().unwrap();
|
||||
|
||||
(value.fract() == 0.0, formatter.format(value))
|
||||
},
|
||||
FloatTy::F64 => {
|
||||
let value = sym_str.parse::<f64>().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<T>(&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),
|
||||
}
|
||||
}
|
||||
}
|
503
src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs
Normal file
503
src/tools/clippy/clippy_lints/src/floating_point_arithmetic.rs
Normal file
|
@ -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<i32> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
200
src/tools/clippy/clippy_lints/src/format.rs
Normal file
200
src/tools/clippy/clippy_lints/src/format.rs
Normal file
|
@ -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<T: LintContext>(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<String> {
|
||||
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, "<arg>");
|
||||
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<String> {
|
||||
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<String> {
|
||||
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
|
||||
}
|
326
src/tools/clippy/clippy_lints/src/formatting.rs
Normal file
326
src/tools/clippy/clippy_lints/src/formatting.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
661
src/tools/clippy/clippy_lints/src/functions.rs
Normal file
661
src/tools/clippy/clippy_lints/src/functions.rs
Normal file
|
@ -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 { 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::<FxHashSet<_>>();
|
||||
|
||||
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<DefId>) -> 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<DefId>) -> 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<hir::HirId> {
|
||||
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<hir::HirId>,
|
||||
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<Self::Map> {
|
||||
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<Self::Map> {
|
||||
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
|
||||
}
|
110
src/tools/clippy/clippy_lints/src/future_not_send.rs
Normal file
110
src/tools/clippy/clippy_lints/src/future_not_send.rs
Normal file
|
@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
src/tools/clippy/clippy_lints/src/get_last_with_len.rs
Normal file
103
src/tools/clippy/clippy_lints/src/get_last_with_len.rs
Normal file
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
src/tools/clippy/clippy_lints/src/identity_conversion.rs
Normal file
124
src/tools/clippy/clippy_lints/src/identity_conversion.rs
Normal file
|
@ -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<HirId>,
|
||||
}
|
||||
|
||||
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, "<expr>").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, "<expr>").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(), "<expr>").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();
|
||||
}
|
||||
}
|
||||
}
|
82
src/tools/clippy/clippy_lints/src/identity_op.rs
Normal file
82
src/tools/clippy/clippy_lints/src/identity_op.rs
Normal file
|
@ -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, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
160
src/tools/clippy/clippy_lints/src/if_let_mutex.rs
Normal file
160
src/tools/clippy/clippy_lints/src/if_let_mutex.rs
Normal file
|
@ -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<Self::Map> {
|
||||
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<Self::Map> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
72
src/tools/clippy/clippy_lints/src/if_let_some_result.rs
Normal file
72
src/tools/clippy/clippy_lints/src/if_let_some_result.rs
Normal file
|
@ -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<T,E>.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
83
src/tools/clippy/clippy_lints/src/if_not_else.rs
Normal file
83
src/tools/clippy/clippy_lints/src/if_not_else.rs
Normal file
|
@ -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<usize> = vec![];
|
||||
/// # fn a() {}
|
||||
/// # fn b() {}
|
||||
/// if !v.is_empty() {
|
||||
/// a()
|
||||
/// } else {
|
||||
/// b()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Could be written:
|
||||
///
|
||||
/// ```rust
|
||||
/// # let v: Vec<usize> = 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`",
|
||||
);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
src/tools/clippy/clippy_lints/src/implicit_return.rs
Normal file
150
src/tools/clippy/clippy_lints/src/implicit_return.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
173
src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs
Normal file
173
src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs
Normal file
|
@ -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,
|
||||
);
|
||||
}
|
193
src/tools/clippy/clippy_lints/src/indexing_slicing.rs
Normal file
193
src/tools/clippy/clippy_lints/src/indexing_slicing.rs
Normal file
|
@ -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<u128>, Option<u128>) {
|
||||
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)
|
||||
}
|
253
src/tools/clippy/clippy_lints/src/infinite_iter.rs
Normal file
253
src/tools/clippy/clippy_lints/src/infinite_iter.rs
Normal file
|
@ -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::<Vec<_>>();
|
||||
/// ```
|
||||
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<bool> 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
|
||||
}
|
94
src/tools/clippy/clippy_lints/src/inherent_impl.rs
Normal file
94
src/tools/clippy/clippy_lints/src/inherent_impl.rs
Normal file
|
@ -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<def_id::DefId, Span>,
|
||||
}
|
||||
|
||||
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");
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue