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:
Oliver Scherer 2020-05-02 09:49:00 +02:00
commit bce9fae97a
1286 changed files with 114475 additions and 0 deletions

View 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"]

View 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
View 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

View 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: -->

View 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
View 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
View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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
}
}

File diff suppressed because it is too large Load diff

View 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

View 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

View 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.

View 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"]

View 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.

View 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
View 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
View 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()
);
}

View 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 = []

View 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)
}

View 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()));
}

View 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();
},
_ => {},
}
}

View 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");
}

View 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
},
}
}

View 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
}

View 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"

View 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.

View 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(())
}

View 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

View file

@ -0,0 +1,3 @@
fn main() {
panic!("This shouldn't even compile")
}

View 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 = []

View file

@ -0,0 +1 @@
This crate contains Clippy lints. For the main crate, check [GitHub](https://github.com/rust-lang/rust-clippy).

View 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
}
}

View 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;
}
}

View 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",
);
}
}
}

View 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
}

View 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
}
}

View 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);
}
}

View 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;
}
}
});
}
}
}

View 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)
}

View 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,
}
}

View 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),
);
}
}
}
}

View 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);
}
}
}
}
}

View 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
}
}

View 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,
}
}

View 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");
}
}
}
}

View 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"];

View 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
}
}

View 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
}
}

View 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,
}
}

View 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 dont 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 dont 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,
}
}

View 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 cant 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
}

View 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",
);
}
}
}
}

View 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)
}

View 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(..) => {},
}
}
}
}
}

View 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"
}

View 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,
);
}
},
_ => (),
}
}

View 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 its 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())
}
}

View 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 wont 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(&current_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(&current);
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),
);
}
}

View 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);
}
}
}

View 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 = &params[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 = &params[1];
if let ExprKind::Paren(_) = param.kind {
span_lint(
cx,
DOUBLE_PARENS,
param.span,
"Consider removing unnecessary double parentheses",
);
}
}
},
_ => {},
}
}
}

View 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
);
}
}
}

View 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));
}
}
}
}
}

View 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,
);
}
}
}
}

View 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;
}
}
}

View 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",
);
}
}
}
}

View 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 = &params[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, &params[0]);
if SpanlessEq::new(self.cx).eq_expr(self.key, &params[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
}
}

View 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",
);
};
}
}
}
}
}

View 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));
}
}

View 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,
}
}

View 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",
);
}
}

View 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
}
}
}

View 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
}

View 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
}

View 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(&param.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),
_ => (),
}
}
}

View 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`");
}
}
}
}
}
}

View 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
}

View 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)");
});
}
}
}
}
}

View 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),
}
}
}

View 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);
}
}
}

View 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
}

View 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
// its 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
}
}

View 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, &param.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
}

View 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 users 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(),
));
}
}
})
},
);
}
}
}
}
}

View 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,
);
}
}
}
}

View 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();
}
}
}

View 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, "..")
),
);
}
}
}

View 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
}
}
}

View 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,
);
}
}
}
}

View 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`",
);
},
_ => (),
}
}
}
}
}

View 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);
}
}
}

View 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,
);
}

View 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)
}

View 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
}

View 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